diff --git a/API.Tests/Parser/ComicParserTests.cs b/API.Tests/Parser/ComicParserTests.cs index fe0cd0961..c2ebc80f2 100644 --- a/API.Tests/Parser/ComicParserTests.cs +++ b/API.Tests/Parser/ComicParserTests.cs @@ -186,6 +186,10 @@ namespace API.Tests.Parser [InlineData("Asterix - HS - Les 12 travaux d'Astérix", true)] [InlineData("Sillage Hors Série - Le Collectionneur - Concordance-DKFR", true)] [InlineData("laughs", false)] + [InlineData("Annual Days of Summer", false)] + [InlineData("Adventure Time 2013 Annual #001 (2013)", true)] + [InlineData("Adventure Time 2013_Annual_#001 (2013)", true)] + [InlineData("Adventure Time 2013_-_Annual #001 (2013)", true)] public void ParseComicSpecialTest(string input, bool expected) { Assert.Equal(expected, !string.IsNullOrEmpty(API.Parser.Parser.ParseComicSpecial(input))); diff --git a/API/Controllers/ReaderController.cs b/API/Controllers/ReaderController.cs index 44d686e22..fbcde4f3c 100644 --- a/API/Controllers/ReaderController.cs +++ b/API/Controllers/ReaderController.cs @@ -365,10 +365,21 @@ namespace API.Controllers public async Task> GetContinuePoint(int seriesId) { var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); - return Ok(await _readerService.GetContinuePoint(seriesId, userId)); } + /// + /// Returns if the user has reading progress on the Series + /// + /// + /// + [HttpGet("has-progress")] + public async Task> HasProgress(int seriesId) + { + var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); + return Ok(await _unitOfWork.AppUserProgressRepository.HasAnyProgressOnSeriesAsync(seriesId, userId)); + } + /// /// Returns a list of bookmarked pages for a given Chapter /// diff --git a/API/Data/Repositories/AppUserProgressRepository.cs b/API/Data/Repositories/AppUserProgressRepository.cs index 37fc68693..d9799aa22 100644 --- a/API/Data/Repositories/AppUserProgressRepository.cs +++ b/API/Data/Repositories/AppUserProgressRepository.cs @@ -12,6 +12,7 @@ public interface IAppUserProgressRepository Task CleanupAbandonedChapters(); Task UserHasProgress(LibraryType libraryType, int userId); Task GetUserProgressAsync(int chapterId, int userId); + Task HasAnyProgressOnSeriesAsync(int seriesId, int userId); } public class AppUserProgressRepository : IAppUserProgressRepository @@ -76,6 +77,12 @@ public class AppUserProgressRepository : IAppUserProgressRepository .AnyAsync(); } + public async Task HasAnyProgressOnSeriesAsync(int seriesId, int userId) + { + return await _context.AppUserProgresses + .AnyAsync(aup => aup.PagesRead > 0 && aup.AppUserId == userId && aup.SeriesId == seriesId); + } + public async Task GetUserProgressAsync(int chapterId, int userId) { return await _context.AppUserProgresses diff --git a/API/Parser/Parser.cs b/API/Parser/Parser.cs index c17290c5b..f93035b31 100644 --- a/API/Parser/Parser.cs +++ b/API/Parser/Parser.cs @@ -484,7 +484,7 @@ namespace API.Parser { // All Keywords, does not account for checking if contains volume/chapter identification. Parser.Parse() will handle. new Regex( - @"(?Specials?|OneShot|One\-Shot|Extra(?:(\sChapter)?[^\S])|Book \d.+?|Compendium \d.+?|Omnibus \d.+?|[_\s\-]TPB[_\s\-]|FCBD \d.+?|Absolute \d.+?|Preview \d.+?|Art Collection|Side(\s|_)Stories|Bonus|Hors Série|(\W|_|-)HS(\W|_|-)|(\W|_|-)THS(\W|_|-))", + @"(?Specials?|OneShot|One\-Shot|\d.+?(\W|_|-)Annual|Annual(\W|_|-)\d.+?|Extra(?:(\sChapter)?[^\S])|Book \d.+?|Compendium \d.+?|Omnibus \d.+?|[_\s\-]TPB[_\s\-]|FCBD \d.+?|Absolute \d.+?|Preview \d.+?|Art Collection|Side(\s|_)Stories|Bonus|Hors Série|(\W|_|-)HS(\W|_|-)|(\W|_|-)THS(\W|_|-))", MatchOptions, RegexTimeout), }; diff --git a/UI/Web/src/app/_services/reader.service.ts b/UI/Web/src/app/_services/reader.service.ts index 849120250..c1db7b1cd 100644 --- a/UI/Web/src/app/_services/reader.service.ts +++ b/UI/Web/src/app/_services/reader.service.ts @@ -103,6 +103,10 @@ export class ReaderService { return this.httpClient.get(this.baseUrl + 'reader/prev-chapter?seriesId=' + seriesId + '&volumeId=' + volumeId + '¤tChapterId=' + currentChapterId); } + hasSeriesProgress(seriesId: number) { + return this.httpClient.get(this.baseUrl + 'reader/has-progress?seriesId=' + seriesId); + } + getCurrentChapter(seriesId: number) { return this.httpClient.get(this.baseUrl + 'reader/continue-point?seriesId=' + seriesId); } diff --git a/UI/Web/src/app/series-detail/series-detail.component.html b/UI/Web/src/app/series-detail/series-detail.component.html index a5ec40dd4..014fa7297 100644 --- a/UI/Web/src/app/series-detail/series-detail.component.html +++ b/UI/Web/src/app/series-detail/series-detail.component.html @@ -62,7 +62,7 @@
-
-
  • +
  • + Storyline + +
    +
    + +
    +
    + +
    +
    +
    +
  • +
  • + Volumes + +
    +
    + +
    +
    +
    +
  • +
  • {{utilityService.formatChapterName(libraryType) + 's'}}
    -
    - -
    = []; activeTabId = 2; - hasNonSpecialVolumeChapters = true; + hasNonSpecialVolumeChapters = false; + hasNonSpecialNonVolumeChapters = false; userReview: string = ''; libraryType: LibraryType = LibraryType.Manga; @@ -223,6 +225,10 @@ export class SeriesDetailComponent implements OnInit, OnDestroy { } } + onNavChange(event: NgbNavChangeEvent) { + this.bulkSelectionService.deselectAll(); + } + handleSeriesActionCallback(action: Action, series: Series) { this.actionInProgress = true; switch(action) { @@ -336,14 +342,14 @@ export class SeriesDetailComponent implements OnInit, OnDestroy { loadSeries(seriesId: number) { this.coverImageOffset = 0; + this.seriesService.getMetadata(seriesId).subscribe(metadata => this.seriesMetadata = metadata); + forkJoin([ this.libraryService.getLibraryType(this.libraryId), - this.seriesService.getMetadata(seriesId), this.seriesService.getSeries(seriesId) ]).subscribe(results => { this.libraryType = results[0]; - this.seriesMetadata = results[1]; - this.series = results[2]; + this.series = results[1]; this.createHTML(); @@ -357,17 +363,19 @@ export class SeriesDetailComponent implements OnInit, OnDestroy { this.seriesService.getVolumes(this.series.id).subscribe(volumes => { - this.chapters = volumes.filter(v => v.number === 0).map(v => v.chapters || []).flat().sort(this.utilityService.sortChapters); - this.volumes = volumes.sort(this.utilityService.sortVolumes); + this.volumes = volumes; // volumes are already be sorted in the backend + const vol0 = this.volumes.filter(v => v.number === 0); + this.storyChapters = vol0.map(v => v.chapters || []).flat().sort(this.utilityService.sortChapters); + this.chapters = volumes.map(v => v.chapters || []).flat().sort(this.utilityService.sortChapters).filter(c => !c.isSpecial || isNaN(parseInt(c.range, 10))); + this.setContinuePoint(); - const vol0 = this.volumes.filter(v => v.number === 0); - this.hasSpecials = vol0.map(v => v.chapters || []).flat().sort(this.utilityService.sortChapters).filter(c => c.isSpecial || isNaN(parseInt(c.range, 10))).length > 0 ; + + const specials = this.storyChapters.filter(c => c.isSpecial || isNaN(parseInt(c.range, 10))); + this.hasSpecials = specials.length > 0 if (this.hasSpecials) { - this.specials = vol0.map(v => v.chapters || []) - .flat() - .filter(c => c.isSpecial || isNaN(parseInt(c.range, 10))) + this.specials = specials .map(c => { c.title = this.utilityService.cleanSpecialTitle(c.title); c.range = this.utilityService.cleanSpecialTitle(c.range); @@ -375,14 +383,30 @@ export class SeriesDetailComponent implements OnInit, OnDestroy { }); } - if (this.volumes.filter(v => v.number !== 0).length === 0 && this.chapters.filter(c => !c.isSpecial).length === 0 && this.specials.length > 0) { - this.activeTabId = 1; - this.hasNonSpecialVolumeChapters = false; + // This shows Chapters/Issues tab + // If this has chapters that are not specials + if (this.chapters.filter(c => !c.isSpecial).length > 0) { + if (this.utilityService.formatChapterName(this.libraryType) == 'Book') { + this.activeTabId = 4; + } + this.hasNonSpecialNonVolumeChapters = true; + } + + // This shows Volumes tab + if (this.volumes.filter(v => v.number !== 0).length !== 0) { + if (this.utilityService.formatChapterName(this.libraryType) == 'Book') { + this.activeTabId = 3; + } + this.hasNonSpecialVolumeChapters = true; } // If an update occured and we were on specials, re-activate Volumes/Chapters - if (!this.hasSpecials && this.activeTabId != 2) { - this.activeTabId = 2; + if (!this.hasSpecials && !this.hasNonSpecialVolumeChapters && this.activeTabId != 2) { + this.activeTabId = 3; + } + + if (this.hasSpecials) { + this.activeTabId = 1; } this.isLoading = false; @@ -397,7 +421,7 @@ export class SeriesDetailComponent implements OnInit, OnDestroy { } setContinuePoint() { - this.hasReadingProgress = this.volumes.filter(v => v.pagesRead > 0).length > 0 || this.chapters.filter(c => c.pagesRead > 0).length > 0; + this.readerService.hasSeriesProgress(this.series.id).subscribe(hasProgress => this.hasReadingProgress = hasProgress); this.readerService.getCurrentChapter(this.series.id).subscribe(chapter => this.currentlyReadingChapter = chapter); }