* Updated number inputs with a more mobile friendly control

* Started writing lots of unit tests on PersonHelper to try and hammer out foreign constraint

* Fixes side-nav actionable alignment

* Added some unit tests

* Buffed out the unit tests

* Applied input modes throughout the app

* Fixed a small bug in refresh token validation to make it work correctly

* Try out a new way to block multithreading from interacting with people during series metadata update.

* Fixed the lock code to properly lock, which should help with any constraint issues.

* Locking notes

* Tweaked locking on people to prevent a constraint issue. This slows down the scanner a bit, but not much. Will tweak after validating on a user's server.

* Replaced all DBFactory.Series with SeriesBuilder.

* Replaced all DBFactory.Volume() with VolumeBuilder

* Replaced SeriesMetadata with Builder

* Replaced DBFactory.CollectionTag

* Lots of refactoring to streamline entity creation

* Fixed one of the unit tests

* Refactored all of new Library()

* Removed tag and genre

* Removed new SeriesMetadata

* Refactored new Volume()

* MangaFile()

* ReadingList()

* Refactored all of Chapter and ReadingList

* Add title to all event widget flows

* Updated Base Url to inform user it doesn't work for docker users with non-root user.

* Added unit test coverage to FormatChapterTitle and FormatChapterName.

* Started on Unit test for scanner, but need to finish it later.

---------

Co-authored-by: Robbie Davis <robbie@therobbiedavis.com>
This commit is contained in:
Joe Milazzo 2023-03-19 12:52:44 -05:00 committed by GitHub
parent eec03d7e96
commit 385f61f9f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
105 changed files with 2257 additions and 2660 deletions

View file

@ -285,9 +285,9 @@ export class AccountService implements OnDestroy {
}
const jwtToken = JSON.parse(atob(this.currentUser.token.split('.')[1]));
// set a timeout to refresh the token a minute before it expires
// set a timeout to refresh the token 10 mins before it expires
const expires = new Date(jwtToken.exp * 1000);
const timeout = expires.getTime() - Date.now() - (60 * 1000);
const timeout = expires.getTime() - Date.now() - (60 * 10000);
this.refreshTokenTimeout = setTimeout(() => this.refreshToken().subscribe(() => {}), timeout);
}

View file

@ -23,7 +23,7 @@
<div class="col-md-6 col-sm-12">
<div class="mb-3" style="width:100%">
<label for="email" class="form-label">Email</label>
<input class="form-control" type="email" id="email" formControlName="email">
<input class="form-control" inputmode="email" type="email" id="email" formControlName="email">
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="userForm.dirty || userForm.touched" [class.is-invalid]="userForm.get('email')?.invalid && userForm.get('email')?.touched">
<div *ngIf="userForm.get('email')?.errors?.required">
This field is required

View file

@ -13,7 +13,7 @@
<div class="row g-0">
<div class="mb-3" style="width:100%">
<label for="email" class="form-label">Email</label>
<input class="form-control" type="email" id="email" formControlName="email" required [class.is-invalid]="inviteForm.get('email')?.invalid && inviteForm.get('email')?.touched">
<input class="form-control" type="email" inputmode="email" id="email" formControlName="email" required [class.is-invalid]="inviteForm.get('email')?.invalid && inviteForm.get('email')?.touched">
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="inviteForm.dirty || inviteForm.touched">
<div *ngIf="email?.errors?.required">
This field is required

View file

@ -13,7 +13,7 @@
<ng-template #emailServiceTooltip>Use fully qualified URL of the email service. Do not include ending slash.</ng-template>
<span class="visually-hidden" id="settings-emailservice-help"><ng-container [ngTemplateOutlet]="emailServiceTooltip"></ng-container></span>
<div class="input-group">
<input id="settings-emailservice" aria-describedby="settings-emailservice-help" class="form-control" formControlName="emailServiceUrl" type="text" aria-describedby="change-bookmarks-dir">
<input id="settings-emailservice" aria-describedby="settings-emailservice-help" class="form-control" formControlName="emailServiceUrl" type="url" autocapitalize="off" inputmode="url" aria-describedby="change-bookmarks-dir">
<button class="btn btn-outline-secondary" (click)="resetEmailServiceUrl()">
Reset
</button>

View file

@ -35,8 +35,8 @@
<div class="mb-3">
<label for="settings-baseurl" class="form-label">Base Url</label>&nbsp;<i class="fa fa-info-circle" placement="right" [ngbTooltip]="baseUrlTooltip" role="button" tabindex="0"></i>
<ng-template #baseUrlTooltip>Use this if you want to host Kavita on a base url ie) yourdomain.com/kavita</ng-template>
<span class="visually-hidden" id="settings-cachedir-help">Use this if you want to host Kavita on a base url ie) yourdomain.com/kavita</span>
<ng-template #baseUrlTooltip>Use this if you want to host Kavita on a base url ie) yourdomain.com/kavita. Not supported on Docker using non-root user.</ng-template>
<span class="visually-hidden" id="settings-cachedir-help">Use this if you want to host Kavita on a base url ie) yourdomain.com/kavita. Not supported on Docker using non-root user.</span>
<input id="settings-baseurl" aria-describedby="settings-baseurl-help" class="form-control" formControlName="baseUrl" type="text"
[class.is-invalid]="settingsForm.get('baseUrl')?.invalid && settingsForm.get('baseUrl')?.touched">
<div id="baseurl-validations" class="invalid-feedback" *ngIf="settingsForm.dirty || settingsForm.touched">
@ -77,7 +77,7 @@
<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"
type="number" inputmode="numeric" step="1" min="1" max="30" onkeypress="return event.charCode >= 48 && event.charCode <= 57"
[class.is-invalid]="settingsForm.get('totalBackups')?.invalid && settingsForm.get('totalBackups')?.touched">
<ng-container *ngIf="settingsForm.get('totalBackups')?.errors as errors">
<p class="invalid-feedback" *ngIf="errors.min">
@ -97,7 +97,7 @@
<ng-template #logTasksTooltip>The number of logs to maintain. Default is 30, minumum is 1, maximum is 30.</ng-template>
<span class="visually-hidden" id="log-tasks-help">The number of backups to maintain. Default is 30, minumum is 1, maximum is 30.</span>
<input id="log-tasks" aria-describedby="log-tasks-help" class="form-control" formControlName="totalLogs"
type="number" step="1" min="1" max="30" onkeypress="return event.charCode >= 48 && event.charCode <= 57"
type="number" inputmode="numeric" step="1" min="1" max="30" onkeypress="return event.charCode >= 48 && event.charCode <= 57"
[class.is-invalid]="settingsForm.get('totalLogs')?.invalid && settingsForm.get('totalLogs')?.touched">
<ng-container *ngIf="settingsForm.get('totalLogs')?.errors as errors">
<p class="invalid-feedback" *ngIf="errors.min">

View file

@ -88,7 +88,7 @@
<label for="release-year" class="form-label">Release Year</label>
<div class="input-group {{metadata.releaseYearLocked ? 'lock-active' : ''}}">
<ng-container [ngTemplateOutlet]="lock" [ngTemplateOutletContext]="{ item: metadata, field: 'releaseYearLocked' }"></ng-container>
<input type="number" class="form-control" id="release-year" formControlName="releaseYear" maxlength="4" minlength="4" [class.is-invalid]="editSeriesForm.get('releaseYear')?.invalid && editSeriesForm.get('releaseYear')?.touched">
<input type="number" inputmode="numeric" class="form-control" id="release-year" formControlName="releaseYear" maxlength="4" minlength="4" [class.is-invalid]="editSeriesForm.get('releaseYear')?.invalid && editSeriesForm.get('releaseYear')?.touched">
<ng-container *ngIf="editSeriesForm.get('releaseYear')?.errors as errors">
<p class="invalid-feedback" *ngIf="errors.pattern">
This must be a valid year greater than 1000 and 4 characters long

View file

@ -92,7 +92,7 @@
<ng-template #progressEvent>
<li class="list-group-item dark-menu-item">
<div class="h6 mb-1">{{message.title}}</div>
<div class="accent-text mb-1" *ngIf="message.subTitle !== ''">{{message.subTitle}}</div>
<div class="accent-text mb-1" *ngIf="message.subTitle !== ''" [title]="message.subTitle">{{message.subTitle}}</div>
<div class="progress-container row g-0 align-items-center">
<div class="col-2">{{prettyPrintProgress(message.body.progress) + '%'}}</div>
<div class="col-10 progress" style="height: 5px;">
@ -122,7 +122,7 @@
<ng-container *ngFor="let download of activeDownloads">
<li class="list-group-item dark-menu-item">
<div class="h6 mb-1">Downloading {{download.entityType | sentenceCase}}</div>
<div class="accent-text mb-1" *ngIf="download.subTitle !== ''">{{download.subTitle}}</div>
<div class="accent-text mb-1" *ngIf="download.subTitle !== ''" [title]="download.subTitle">{{download.subTitle}}</div>
<div class="progress-container row g-0 align-items-center">
<div class="col-2">{{download.progress}}%</div>
<div class="col-10 progress" style="height: 5px;">

View file

@ -1,7 +1,7 @@
<form [formGroup]="typeaheadForm" class="grouped-typeahead">
<div class="typeahead-input" [ngClass]="{'focused': hasFocus}" (click)="onInputFocus($event)">
<div class="search">
<input #input [id]="id" type="text" autocomplete="off" formControlName="typeahead" [placeholder]="placeholder"
<input #input [id]="id" type="text" inputmode="search" autocomplete="off" formControlName="typeahead" [placeholder]="placeholder"
aria-haspopup="listbox" aria-owns="dropdown" aria-expanded="hasFocus && (grouppedData.persons.length || grouppedData.collections.length || grouppedData.series.length || grouppedData.persons.length || grouppedData.tags.length || grouppedData.genres.length)"
aria-autocomplete="list" (focusout)="close($event)" (focus)="open($event)" role="search"
>

View file

@ -8,7 +8,7 @@
<div class="me-3 align-middle">
<div style="padding-top: 40px">
<label for="reorder-{{i}}" class="form-label visually-hidden">Reorder</label>
<input *ngIf="accessibilityMode" id="reorder-{{i}}" class="form-control" type="number" min="0" [max]="items.length - 1" [value]="i" style="width: 60px"
<input *ngIf="accessibilityMode" id="reorder-{{i}}" class="form-control" type="number" inputmode="numeric" min="0" [max]="items.length - 1" [value]="i" style="width: 60px"
(focusout)="updateIndex(i, item)" (keydown.enter)="updateIndex(i, item)" aria-describedby="instructions">
</div>
</div>
@ -31,7 +31,7 @@
<div class="me-3 align-middle">
<div class="align-middle" style="padding-top: 40px" *ngIf="accessibilityMode">
<label for="reorder-{{i}}" class="form-label visually-hidden">Reorder</label>
<input id="reorder-{{i}}" class="form-control" type="number" min="0" [max]="items.length - 1" [value]="i" style="width: 60px"
<input id="reorder-{{i}}" class="form-control" type="number" inputmode="numeric" min="0" [max]="items.length - 1" [value]="i" style="width: 60px"
(focusout)="updateIndex(i, item)" (keydown.enter)="updateIndex(i, item)" aria-describedby="instructions">
</div>
<i *ngIf="!accessibilityMode" class="fa fa-grip-vertical drag-handle" aria-hidden="true" cdkDragHandle></i>

View file

@ -43,7 +43,7 @@
<div class="col-md-6 col-sm-12" *ngIf="reviewGroup.get('startingMonth') as formControl" style="width: 90%">
<label for="start-month" class="form-label">Month</label>
<input id="start-month" class="form-control" formControlName="startingMonth"
type="number" [class.is-invalid]="formControl?.invalid && formControl?.touched"
type="number" inputmode="numeric" [class.is-invalid]="formControl?.invalid && formControl?.touched"
aria-describedby="starting-year-header">
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="reviewGroup.dirty || reviewGroup.touched">
<div *ngIf="formControl.errors?.min || formControl.errors?.max">
@ -53,7 +53,7 @@
</div>
<div class="col-md-6 col-sm-12" *ngIf="reviewGroup.get('startingYear') as formControl" style="width: 90%">
<label for="start-year" class="form-label">Year</label>
<input id="start-year" class="form-control" formControlName="startingYear" type="number"
<input id="start-year" class="form-control" formControlName="startingYear" type="number" inputmode="numeric"
[class.is-invalid]="formControl.invalid && formControl.touched"
aria-describedby="starting-year-header">
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="reviewGroup.dirty || reviewGroup.touched">
@ -68,7 +68,7 @@
<h6 id="ending-year-heading">Ending</h6>
<div class="col-md-6 col-sm-12" *ngIf="reviewGroup.get('endingMonth') as formControl" style="width: 90%">
<label for="library-name" class="form-label">Month</label>
<input id="library-name" class="form-control" formControlName="endingMonth" type="number"
<input id="library-name" class="form-control" formControlName="endingMonth" type="number" inputmode="numeric"
[class.is-invalid]="formControl?.invalid && formControl?.touched"
aria-describedby="ending-year-header">
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="reviewGroup.dirty || reviewGroup.touched">
@ -79,7 +79,7 @@
</div>
<div class="col-md-6 col-sm-12" *ngIf="reviewGroup.get('endingYear') as formControl" style="width: 90%">
<label for="library-name" class="form-label">Year</label>
<input id="library-name" class="form-control" formControlName="endingYear" type="number"
<input id="library-name" class="form-control" formControlName="endingYear" type="number" inputmode="numeric"
[class.is-invalid]="formControl?.invalid && formControl?.touched"
aria-describedby="ending-year-header">
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="reviewGroup.dirty || reviewGroup.touched">

View file

@ -23,7 +23,7 @@
<div class="mb-3" style="width:100%">
<label for="email" class="form-label">Email</label>
<input class="form-control" type="email" id="email" formControlName="email" required readonly
<input class="form-control" type="email" inputmode="email" id="email" formControlName="email" required readonly
[class.is-invalid]="registerForm.get('email')?.invalid && registerForm.get('email')?.touched">
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="registerForm.dirty || registerForm.touched">
<div *ngIf="registerForm.get('email')?.errors?.required">

View file

@ -20,7 +20,7 @@
<span class="visually-hidden" id="email-help">
<ng-container [ngTemplateOutlet]="emailTooltip"></ng-container>
</span>
<input class="form-control" type="email" id="email" formControlName="email" required aria-describedby="email-help"
<input class="form-control" type="email" inputmode="email" id="email" formControlName="email" required aria-describedby="email-help"
[class.is-invalid]="registerForm.get('email')?.invalid && registerForm.get('email')?.touched">
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="registerForm.dirty || registerForm.touched">
<div *ngIf="registerForm.get('email')?.errors?.required">

View file

@ -5,7 +5,7 @@
<form [formGroup]="registerForm" (ngSubmit)="submit()">
<div class="mb-3" style="width:100%">
<label for="email" class="form-label">Email</label>
<input class="form-control custom-input" type="email" id="email" formControlName="email" [class.is-invalid]="registerForm.get('email')?.invalid && registerForm.get('email')?.touched">
<input class="form-control custom-input" type="email" inputmode="email" id="email" formControlName="email" [class.is-invalid]="registerForm.get('email')?.invalid && registerForm.get('email')?.touched">
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="registerForm.dirty || registerForm.touched">
<div *ngIf="registerForm.get('email')?.errors?.required">
This field is required

View file

@ -24,7 +24,7 @@
<div class="mb-3" style="width:100%">
<label for="email" class="form-label">Email</label>
<input class="form-control" type="email" id="email" formControlName="email" [class.is-invalid]="registerForm.get('email')?.invalid && registerForm.get('email')?.touched">
<input class="form-control" type="email" inputmode="email" id="email" formControlName="email" [class.is-invalid]="registerForm.get('email')?.invalid && registerForm.get('email')?.touched">
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="registerForm.dirty || registerForm.touched">
<div *ngIf="registerForm.get('email')?.errors?.required">
This field is required

View file

@ -27,6 +27,8 @@
span {
display:flex;
&:last-child {
flex-grow: 1;
justify-content: end;

View file

@ -17,7 +17,7 @@
<span class="visually-hidden" id="email-help">The number of backups to maintain. Default is 30, minumum is 1, maximum is 30.</span>
<input id="email" aria-describedby="email-help"
class="form-control" formControlName="email" type="email"
class="form-control" formControlName="email" type="email" inputmode="email"
placeholder="id@kindle.com" [class.is-invalid]="settingsForm.get('email')?.invalid && settingsForm.get('email')?.touched">
<ng-container *ngIf="settingsForm.get('email')?.errors as errors">
<p class="invalid-feedback" *ngIf="errors.email">