Release Shakeout Part 1 (#1440)

* Bumped docnet back up, as user issue was not related to the version. Reworked the logic flow for ConfirmEmailToken. Added new test cases for a bug reported around Docnet and weird characters.

* Removed a duplicate remove from want to read list

* Fixed an issue where series detail didn't appopriately handle remove from want to read.

* Added pagination information to want to read, fixed remove from want to read not reloading page

* When clearing a series of bookmarks, automatically refresh page.

* Added a continue button on reading list page so user can continue where they left off (progress) or start at beginning

* Added todo about design idea

* Added a bug marker
This commit is contained in:
Joseph Milazzo 2022-08-16 14:04:21 -05:00 committed by GitHub
parent a3a0b61fc0
commit 9d90652792
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 57 additions and 42 deletions

View File

@ -22,16 +22,18 @@ namespace API.Tests.Services
[InlineData("The Golden Harpoon; Or, Lost Among the Floes A Story of the Whaling Grounds.epub", 16)] [InlineData("The Golden Harpoon; Or, Lost Among the Floes A Story of the Whaling Grounds.epub", 16)]
[InlineData("Non-existent file.epub", 0)] [InlineData("Non-existent file.epub", 0)]
[InlineData("Non an ebub.pdf", 0)] [InlineData("Non an ebub.pdf", 0)]
[InlineData("test_ſ.pdf", 1)] // This is dependent on Docnet bug https://github.com/GowenGit/docnet/issues/80
[InlineData("test.pdf", 1)]
public void GetNumberOfPagesTest(string filePath, int expectedPages) public void GetNumberOfPagesTest(string filePath, int expectedPages)
{ {
var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/BookService/EPUB"); var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/BookService");
Assert.Equal(expectedPages, _bookService.GetNumberOfPages(Path.Join(testDirectory, filePath))); Assert.Equal(expectedPages, _bookService.GetNumberOfPages(Path.Join(testDirectory, filePath)));
} }
[Fact] [Fact]
public void ShouldHaveComicInfo() public void ShouldHaveComicInfo()
{ {
var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/BookService/EPUB"); var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/BookService");
var archive = Path.Join(testDirectory, "The Golden Harpoon; Or, Lost Among the Floes A Story of the Whaling Grounds.epub"); var archive = Path.Join(testDirectory, "The Golden Harpoon; Or, Lost Among the Floes A Story of the Whaling Grounds.epub");
const string summaryInfo = "Book Description"; const string summaryInfo = "Book Description";
@ -44,7 +46,7 @@ namespace API.Tests.Services
[Fact] [Fact]
public void ShouldHaveComicInfo_WithAuthors() public void ShouldHaveComicInfo_WithAuthors()
{ {
var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/BookService/EPUB"); var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/BookService");
var archive = Path.Join(testDirectory, "The Golden Harpoon; Or, Lost Among the Floes A Story of the Whaling Grounds.epub"); var archive = Path.Join(testDirectory, "The Golden Harpoon; Or, Lost Among the Floes A Story of the Whaling Grounds.epub");
var comicInfo = _bookService.GetComicInfo(archive); var comicInfo = _bookService.GetComicInfo(archive);
@ -52,16 +54,5 @@ namespace API.Tests.Services
Assert.Equal("Roger Starbuck,Junya Inoue", comicInfo.Writer); Assert.Equal("Roger Starbuck,Junya Inoue", comicInfo.Writer);
} }
#region BookEscaping
[Fact]
public void EscapeCSSImportReferencesTest()
{
}
#endregion
} }
} }

View File

@ -2014,7 +2014,6 @@ public class ReaderServiceTests
#endregion #endregion
#region MarkSeriesAsRead #region MarkSeriesAsRead
[Fact] [Fact]

Binary file not shown.

Binary file not shown.

View File

@ -40,7 +40,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="11.0.0" /> <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="11.0.0" />
<PackageReference Include="Docnet.Core" Version="2.4.0-alpha.2" /> <PackageReference Include="Docnet.Core" Version="2.4.0-alpha.4" />
<PackageReference Include="ExCSS" Version="4.1.0" /> <PackageReference Include="ExCSS" Version="4.1.0" />
<PackageReference Include="Flurl" Version="3.0.6" /> <PackageReference Include="Flurl" Version="3.0.6" />
<PackageReference Include="Flurl.Http" Version="3.2.4" /> <PackageReference Include="Flurl.Http" Version="3.2.4" />

View File

@ -727,21 +727,18 @@ namespace API.Controllers
private async Task<bool> ConfirmEmailToken(string token, AppUser user) private async Task<bool> ConfirmEmailToken(string token, AppUser user)
{ {
var result = await _userManager.ConfirmEmailAsync(user, token); var result = await _userManager.ConfirmEmailAsync(user, token);
if (!result.Succeeded) if (result.Succeeded) return true;
{
_logger.LogCritical("Email validation failed"); _logger.LogCritical("[Account] Email validation failed");
if (result.Errors.Any()) if (!result.Errors.Any()) return false;
{
foreach (var error in result.Errors) foreach (var error in result.Errors)
{ {
_logger.LogCritical("Email validation error: {Message}", error.Description); _logger.LogCritical("[Account] Email validation error: {Message}", error.Description);
}
} }
return false; return false;
}
return true;
} }
} }
} }

View File

@ -9,6 +9,8 @@
[isLoading]="loadingBookmarks" [isLoading]="loadingBookmarks"
[items]="series" [items]="series"
[trackByIdentity]="trackByIdentity" [trackByIdentity]="trackByIdentity"
[refresh]="refresh"
> >
<ng-template #cardItem let-item let-position="idx"> <ng-template #cardItem let-item let-position="idx">
<app-card-item [entity]="item" (reload)="loadBookmarks()" [title]="item.name" [imageUrl]="imageService.getSeriesCoverImage(item.id)" <app-card-item [entity]="item" (reload)="loadBookmarks()" [title]="item.name" [imageUrl]="imageService.getSeriesCoverImage(item.id)"

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, OnDestroy, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, HostListener, OnDestroy, OnInit } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr'; import { ToastrService } from 'ngx-toastr';
import { take, Subject } from 'rxjs'; import { take, Subject } from 'rxjs';
@ -30,6 +30,7 @@ export class BookmarksComponent implements OnInit, OnDestroy {
actions: ActionItem<Series>[] = []; actions: ActionItem<Series>[] = [];
trackByIdentity = (index: number, item: Series) => `${item.name}_${item.localizedName}_${item.pagesRead}`; trackByIdentity = (index: number, item: Series) => `${item.name}_${item.localizedName}_${item.pagesRead}`;
refresh: EventEmitter<void> = new EventEmitter();
private onDestroy: Subject<void> = new Subject<void>(); private onDestroy: Subject<void> = new Subject<void>();
@ -153,6 +154,7 @@ export class BookmarksComponent implements OnInit, OnDestroy {
} }
this.clearingSeries[series.id] = false; this.clearingSeries[series.id] = false;
this.toastr.success(series.name + '\'s bookmarks have been removed'); this.toastr.success(series.name + '\'s bookmarks have been removed');
this.refresh.emit();
this.cdRef.markForCheck(); this.cdRef.markForCheck();
}); });
} }

View File

@ -146,14 +146,7 @@ export class BulkSelectionService {
// else returns volume/chapter items // else returns volume/chapter items
const allowedActions = [Action.AddToReadingList, Action.MarkAsRead, Action.MarkAsUnread, Action.AddToCollection, Action.Delete, Action.AddToWantToReadList, Action.RemoveFromWantToReadList]; const allowedActions = [Action.AddToReadingList, Action.MarkAsRead, Action.MarkAsUnread, Action.AddToCollection, Action.Delete, Action.AddToWantToReadList, Action.RemoveFromWantToReadList];
if (Object.keys(this.selectedCards).filter(item => item === 'series').length > 0) { if (Object.keys(this.selectedCards).filter(item => item === 'series').length > 0) {
let actions = this.actionFactory.getSeriesActions(callback).filter(item => allowedActions.includes(item.action)); return this.actionFactory.getSeriesActions(callback).filter(item => allowedActions.includes(item.action));
if (this.activeRoute.startsWith('/want-to-read')) {
const removeFromWantToRead = {...actions[0]};
removeFromWantToRead.action = Action.RemoveFromWantToReadList;
removeFromWantToRead.title = 'Remove from Want to Read';
actions.push(removeFromWantToRead);
}
return actions;
} }
if (Object.keys(this.selectedCards).filter(item => item === 'bookmark').length > 0) { if (Object.keys(this.selectedCards).filter(item => item === 'bookmark').length > 0) {

View File

@ -44,6 +44,7 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy, OnChanges,
@Input() actions: ActionItem<any>[] = []; @Input() actions: ActionItem<any>[] = [];
@Input() trackByIdentity!: TrackByFunction<any>; //(index: number, item: any) => string @Input() trackByIdentity!: TrackByFunction<any>; //(index: number, item: any) => string
@Input() filterSettings!: FilterSettings; @Input() filterSettings!: FilterSettings;
@Input() refresh!: EventEmitter<void>;
@Input() jumpBarKeys: Array<JumpKey> = []; // This is aprox 784 pixels wide @Input() jumpBarKeys: Array<JumpKey> = []; // This is aprox 784 pixels wide
@ -100,6 +101,13 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy, OnChanges,
this.pagination = {currentPage: 1, itemsPerPage: this.items.length, totalItems: this.items.length, totalPages: 1}; this.pagination = {currentPage: 1, itemsPerPage: this.items.length, totalItems: this.items.length, totalPages: 1};
this.changeDetectionRef.markForCheck(); this.changeDetectionRef.markForCheck();
} }
if (this.refresh) {
this.refresh.subscribe(() => {
this.changeDetectionRef.markForCheck();
this.virtualScroller.refresh();
});
}
} }
ngAfterViewInit(): void { ngAfterViewInit(): void {

View File

@ -1,8 +1,8 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output } from '@angular/core'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output } from '@angular/core';
import { Router } from '@angular/router'; import { NavigationStart, Router } from '@angular/router';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { ToastrService } from 'ngx-toastr'; import { ToastrService } from 'ngx-toastr';
import { take } from 'rxjs/operators'; import { filter, take } from 'rxjs/operators';
import { Series } from 'src/app/_models/series'; import { Series } from 'src/app/_models/series';
import { AccountService } from 'src/app/_services/account.service'; import { AccountService } from 'src/app/_services/account.service';
import { ImageService } from 'src/app/_services/image.service'; import { ImageService } from 'src/app/_services/image.service';
@ -102,6 +102,9 @@ export class SeriesCardComponent implements OnInit, OnChanges, OnDestroy {
break; break;
case Action.RemoveFromWantToReadList: case Action.RemoveFromWantToReadList:
this.actionService.removeMultipleSeriesFromWantToReadList([series.id]); this.actionService.removeMultipleSeriesFromWantToReadList([series.id]);
if (this.router.url.startsWith('/want-to-read')) {
this.reload.emit(true);
}
break; break;
case(Action.AddToCollection): case(Action.AddToCollection):
this.actionService.addMultipleSeriesToCollectionTag([series]); this.actionService.addMultipleSeriesToCollectionTag([series]);

View File

@ -1,4 +1,5 @@
<div cdkDropList class="{{items.length > 0 ? 'example-list list-group-flush' : ''}}" (cdkDropListDropped)="drop($event)"> <div cdkDropList class="{{items.length > 0 ? 'example-list list-group-flush' : ''}}" (cdkDropListDropped)="drop($event)">
<!-- BUG: https://github.com/angular/components/issues/14098 -->
<div class="example-box" *ngFor="let item of items; index as i" cdkDrag [cdkDragData]="item" cdkDragBoundary=".example-list"> <div class="example-box" *ngFor="let item of items; index as i" cdkDrag [cdkDragData]="item" cdkDragBoundary=".example-list">
<div class="d-flex list-container"> <div class="d-flex list-container">
<div class="me-3 align-middle"> <div class="me-3 align-middle">

View File

@ -18,10 +18,16 @@
<div class="row g-0 mb-3"> <div class="row g-0 mb-3">
<div class="col-auto me-2"> <div class="col-auto me-2">
<!-- Action row--> <!-- Action row-->
<button class="btn btn-primary" title="Read" (click)="read()"> <button class="btn btn-primary" title="Read from beginning" (click)="read()">
<span>
<i class="fa fa-book" aria-hidden="true"></i>
<span class="read-btn--text">&nbsp;Read</span>
</span>
</button>
<button class="btn btn-primary ms-2" title="Continue from last reading position" (click)="continue()">
<span> <span>
<i class="fa fa-book-open" aria-hidden="true"></i> <i class="fa fa-book-open" aria-hidden="true"></i>
<span class="read-btn--text">&nbsp;Read</span> <span class="read-btn--text">&nbsp;Continue</span>
</span> </span>
</button> </button>
</div> </div>
@ -33,6 +39,7 @@
<span class="read-btn--text">&nbsp;Remove Read</span> <span class="read-btn--text">&nbsp;Remove Read</span>
</button> </button>
</div> </div>
<!-- TODO: Move this in companion bar's page actions -->
<div class="col-auto ms-2 mt-2" *ngIf="!(readingList?.promoted && !this.isAdmin)"> <div class="col-auto ms-2 mt-2" *ngIf="!(readingList?.promoted && !this.isAdmin)">
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="accessibilit-mode" [value]="accessibilityMode" (change)="accessibilityMode = !accessibilityMode"> <input class="form-check-input" type="checkbox" id="accessibilit-mode" [value]="accessibilityMode" (change)="accessibilityMode = !accessibilityMode">

View File

@ -176,6 +176,12 @@ export class ReadingListDetailComponent implements OnInit {
} }
read() { read() {
if (!this.readingList) return;
const firstItem = this.items[0];
this.router.navigate(this.readerService.getNavigationArray(firstItem.libraryId, firstItem.seriesId, firstItem.chapterId, firstItem.seriesFormat), {queryParams: {readingListId: this.readingList.id}});
}
continue() {
// TODO: Can I do this in the backend? // TODO: Can I do this in the backend?
if (!this.readingList) return; if (!this.readingList) return;
let currentlyReadingChapter = this.items[0]; let currentlyReadingChapter = this.items[0];

View File

@ -384,6 +384,12 @@ export class SeriesDetailComponent implements OnInit, OnDestroy, AfterContentChe
this.changeDetectionRef.markForCheck(); this.changeDetectionRef.markForCheck();
}); });
break; break;
case Action.RemoveFromWantToReadList:
this.actionService.removeMultipleSeriesFromWantToReadList([series.id], () => {
this.actionInProgress = false;
this.changeDetectionRef.markForCheck();
});
break;
case (Action.Download): case (Action.Download):
if (this.downloadInProgress) return; if (this.downloadInProgress) return;
this.downloadSeries(); this.downloadSeries();

View File

@ -5,6 +5,7 @@
Want To Read Want To Read
</h2> </h2>
</ng-container> </ng-container>
<h6 subtitle>{{seriesPagination.totalItems}} Series</h6>
</app-side-nav-companion-bar> </app-side-nav-companion-bar>
</div> </div>

View File

@ -43,7 +43,6 @@ export class WantToReadComponent implements OnInit, OnDestroy {
filterOpen: EventEmitter<boolean> = new EventEmitter(); filterOpen: EventEmitter<boolean> = new EventEmitter();
private onDestory: Subject<void> = new Subject<void>(); private onDestory: Subject<void> = new Subject<void>();
trackByIdentity = (index: number, item: Series) => `${item.name}_${item.localizedName}_${item.pagesRead}`; trackByIdentity = (index: number, item: Series) => `${item.name}_${item.localizedName}_${item.pagesRead}`;