diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 98ce4c439..8a6b9c2f6 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -10,12 +10,12 @@ jobs: runs-on: windows-latest steps: - name: Checkout Repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup .NET Core - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: 8.0.x @@ -26,7 +26,7 @@ jobs: - name: Install dependencies run: dotnet restore - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: csproj path: Kavita.Common/Kavita.Common.csproj diff --git a/.github/workflows/canary-workflow.yml b/.github/workflows/canary-workflow.yml index af4a45dec..32eb2d01f 100644 --- a/.github/workflows/canary-workflow.yml +++ b/.github/workflows/canary-workflow.yml @@ -12,11 +12,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: csproj path: Kavita.Common/Kavita.Common.csproj @@ -26,12 +26,12 @@ jobs: needs: [ build ] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup .NET Core - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: 8.0.x @@ -59,14 +59,14 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} - name: Check Out Repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: canary - name: NodeJS to Compile WebUI - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: '18.13.x' + node-version: 20 - run: | cd UI/Web || exit echo 'Installing web dependencies' @@ -81,7 +81,7 @@ jobs: cd ../ || exit - name: Get csproj Version - uses: kzrnm/get-net-sdk-project-versions-action@v1 + uses: kzrnm/get-net-sdk-project-versions-action@v2 id: get-version with: proj-path: Kavita.Common/Kavita.Common.csproj @@ -96,7 +96,7 @@ jobs: run: echo "${{steps.get-version.outputs.assembly-version}}" - name: Compile dotnet app - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: 8.0.x @@ -106,28 +106,28 @@ jobs: - run: ./monorepo-build.sh - name: Login to Docker Hub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - name: Login to GitHub Container Registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Build and push id: docker_build - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . platforms: linux/amd64,linux/arm/v7,linux/arm64 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 53103f850..6f77e6547 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -46,7 +46,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Swashbuckle CLI shell: bash diff --git a/.github/workflows/develop-workflow.yml b/.github/workflows/develop-workflow.yml index ec31eae9c..d97b7f8cf 100644 --- a/.github/workflows/develop-workflow.yml +++ b/.github/workflows/develop-workflow.yml @@ -21,11 +21,11 @@ jobs: if: github.ref == 'refs/heads/develop' steps: - name: Checkout Repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: csproj path: Kavita.Common/Kavita.Common.csproj @@ -36,12 +36,12 @@ jobs: runs-on: ubuntu-latest if: github.ref == 'refs/heads/develop' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup .NET Core - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: 8.0.x @@ -89,14 +89,14 @@ jobs: echo "BODY=$body" >> $GITHUB_OUTPUT - name: Check Out Repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: develop - name: NodeJS to Compile WebUI - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: '18.13.x' + node-version: 20 - run: | cd UI/Web || exit echo 'Installing web dependencies' @@ -111,7 +111,7 @@ jobs: cd ../ || exit - name: Get csproj Version - uses: kzrnm/get-net-sdk-project-versions-action@v1 + uses: kzrnm/get-net-sdk-project-versions-action@v2 id: get-version with: proj-path: Kavita.Common/Kavita.Common.csproj @@ -126,7 +126,7 @@ jobs: run: echo "${{steps.get-version.outputs.assembly-version}}" - name: Compile dotnet app - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: 8.0.x @@ -136,28 +136,28 @@ jobs: - run: ./monorepo-build.sh - name: Login to Docker Hub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - name: Login to GitHub Container Registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Build and push id: docker_build - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . platforms: linux/amd64,linux/arm/v7,linux/arm64 diff --git a/.github/workflows/release-workflow.yml b/.github/workflows/release-workflow.yml index ca1314e8b..84a381e0b 100644 --- a/.github/workflows/release-workflow.yml +++ b/.github/workflows/release-workflow.yml @@ -30,11 +30,11 @@ jobs: if: github.event.pull_request.merged == true && contains(github.head_ref, 'release') steps: - name: Checkout Repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: csproj path: Kavita.Common/Kavita.Common.csproj @@ -77,14 +77,14 @@ jobs: echo "BODY=$body" >> $GITHUB_OUTPUT - name: Check Out Repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: develop - name: NodeJS to Compile WebUI - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: '18.13.x' + node-version: 20 - run: | cd UI/Web || exit @@ -100,7 +100,7 @@ jobs: cd ../ || exit - name: Get csproj Version - uses: kzrnm/get-net-sdk-project-versions-action@v1 + uses: kzrnm/get-net-sdk-project-versions-action@v2 id: get-version with: proj-path: Kavita.Common/Kavita.Common.csproj @@ -117,7 +117,7 @@ jobs: id: parse-version - name: Compile dotnet app - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: 8.0.x - name: Install Swashbuckle CLI @@ -126,28 +126,28 @@ jobs: - run: ./monorepo-build.sh - name: Login to Docker Hub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - name: Login to GitHub Container Registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Build and push stable id: docker_build_stable - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . platforms: linux/amd64,linux/arm/v7,linux/arm64 @@ -156,7 +156,7 @@ jobs: - name: Build and push nightly id: docker_build_nightly - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . platforms: linux/amd64,linux/arm/v7,linux/arm64 diff --git a/API.Tests/Parsing/MangaParsingTests.cs b/API.Tests/Parsing/MangaParsingTests.cs index dcb3501e1..df09c0ebb 100644 --- a/API.Tests/Parsing/MangaParsingTests.cs +++ b/API.Tests/Parsing/MangaParsingTests.cs @@ -206,6 +206,7 @@ public class MangaParsingTests [InlineData("test 2 years 1권", "test 2 years")] [InlineData("test 2 years 1화", "test 2 years")] [InlineData("Nagasarete Airantou - Vol. 30 Ch. 187.5 - Vol.30 Omake", "Nagasarete Airantou")] + [InlineData("Cynthia The Mission - c000 - c006 (v06)", "Cynthia The Mission")] public void ParseSeriesTest(string filename, string expected) { Assert.Equal(expected, API.Services.Tasks.Scanner.Parser.Parser.ParseSeries(filename)); diff --git a/API/API.csproj b/API/API.csproj index 42f11e012..a3eb80a22 100644 --- a/API/API.csproj +++ b/API/API.csproj @@ -66,7 +66,7 @@ - + diff --git a/API/Data/ManualMigrations/ManualMigrateLooseLeafChapters.cs b/API/Data/ManualMigrations/ManualMigrateLooseLeafChapters.cs index 370e189ba..93fc569e8 100644 --- a/API/Data/ManualMigrations/ManualMigrateLooseLeafChapters.cs +++ b/API/Data/ManualMigrations/ManualMigrateLooseLeafChapters.cs @@ -113,7 +113,35 @@ public static class MigrateLooseLeafChapters //UpdateCoverImage(directoryService, logger, chapter, extension, newVolume); } - // Update the progress table with the new VolumeId + + var oldVolumeBookmarks = await dataContext.AppUserBookmark + .Where(p => p.VolumeId == distinctVolume.Volume.Id).ToListAsync(); + logger.LogInformation("Moving {Count} existing Bookmarks from Volume Id {OldVolumeId} to New Volume {NewVolumeId}", + oldVolumeBookmarks.Count, distinctVolume.Volume.Id, newVolume.Id); + foreach (var bookmark in oldVolumeBookmarks) + { + bookmark.VolumeId = newVolume.Id; + } + + + var oldVolumePersonalToC = await dataContext.AppUserTableOfContent + .Where(p => p.VolumeId == distinctVolume.Volume.Id).ToListAsync(); + logger.LogInformation("Moving {Count} existing Personal ToC from Volume Id {OldVolumeId} to New Volume {NewVolumeId}", + oldVolumePersonalToC.Count, distinctVolume.Volume.Id, newVolume.Id); + foreach (var pToc in oldVolumePersonalToC) + { + pToc.VolumeId = newVolume.Id; + } + + var oldVolumeReadingListItems = await dataContext.ReadingListItem + .Where(p => p.VolumeId == distinctVolume.Volume.Id).ToListAsync(); + logger.LogInformation("Moving {Count} existing Personal ToC from Volume Id {OldVolumeId} to New Volume {NewVolumeId}", + oldVolumeReadingListItems.Count, distinctVolume.Volume.Id, newVolume.Id); + foreach (var readingListItem in oldVolumeReadingListItems) + { + readingListItem.VolumeId = newVolume.Id; + } + await dataContext.SaveChangesAsync(); } diff --git a/API/Data/ManualMigrations/ManualMigrateMixedSpecials.cs b/API/Data/ManualMigrations/ManualMigrateMixedSpecials.cs index bba19b9ae..4e22abfb8 100644 --- a/API/Data/ManualMigrations/ManualMigrateMixedSpecials.cs +++ b/API/Data/ManualMigrations/ManualMigrateMixedSpecials.cs @@ -129,6 +129,35 @@ public static class MigrateMixedSpecials //UpdateCoverImage(directoryService, logger, specialChapter, extension, newVolume); } + + var oldVolumeBookmarks = await dataContext.AppUserBookmark + .Where(p => p.VolumeId == distinctVolume.Volume.Id).ToListAsync(); + logger.LogInformation("Moving {Count} existing Bookmarks from Volume Id {OldVolumeId} to New Volume {NewVolumeId}", + oldVolumeBookmarks.Count, distinctVolume.Volume.Id, newVolume.Id); + foreach (var bookmark in oldVolumeBookmarks) + { + bookmark.VolumeId = newVolume.Id; + } + + + var oldVolumePersonalToC = await dataContext.AppUserTableOfContent + .Where(p => p.VolumeId == distinctVolume.Volume.Id).ToListAsync(); + logger.LogInformation("Moving {Count} existing Personal ToC from Volume Id {OldVolumeId} to New Volume {NewVolumeId}", + oldVolumePersonalToC.Count, distinctVolume.Volume.Id, newVolume.Id); + foreach (var pToc in oldVolumePersonalToC) + { + pToc.VolumeId = newVolume.Id; + } + + var oldVolumeReadingListItems = await dataContext.ReadingListItem + .Where(p => p.VolumeId == distinctVolume.Volume.Id).ToListAsync(); + logger.LogInformation("Moving {Count} existing Personal ToC from Volume Id {OldVolumeId} to New Volume {NewVolumeId}", + oldVolumeReadingListItems.Count, distinctVolume.Volume.Id, newVolume.Id); + foreach (var readingListItem in oldVolumeReadingListItems) + { + readingListItem.VolumeId = newVolume.Id; + } + await dataContext.SaveChangesAsync(); } diff --git a/API/Entities/AppUserProgress.cs b/API/Entities/AppUserProgress.cs index c972af78a..edbd25aa7 100644 --- a/API/Entities/AppUserProgress.cs +++ b/API/Entities/AppUserProgress.cs @@ -7,7 +7,7 @@ namespace API.Entities; /// /// Represents the progress a single user has on a given Chapter. /// -public class AppUserProgress : IEntityDate +public class AppUserProgress { /// /// Id of Entity @@ -59,4 +59,10 @@ public class AppUserProgress : IEntityDate /// User this progress belongs to /// public int AppUserId { get; set; } + + public void MarkModified() + { + LastModified = DateTime.Now; + LastModifiedUtc = DateTime.UtcNow; + } } diff --git a/API/Extensions/ChapterListExtensions.cs b/API/Extensions/ChapterListExtensions.cs index db707a5f9..a1dc510bb 100644 --- a/API/Extensions/ChapterListExtensions.cs +++ b/API/Extensions/ChapterListExtensions.cs @@ -31,7 +31,7 @@ public static class ChapterListExtensions { var normalizedPath = Parser.NormalizePath(info.FullFilePath); var specialTreatment = info.IsSpecialInfo(); - return specialTreatment + return specialTreatment ? chapters.FirstOrDefault(c => c.Range == Parser.RemoveExtensionIfSupported(info.Filename) || c.Files.Select(f => Parser.NormalizePath(f.FilePath)).Contains(normalizedPath)) : chapters.FirstOrDefault(c => c.Range == info.Chapters); } diff --git a/API/Extensions/QueryExtensions/Filtering/SeriesSort.cs b/API/Extensions/QueryExtensions/Filtering/SeriesSort.cs index 4913c4059..efc4bc670 100644 --- a/API/Extensions/QueryExtensions/Filtering/SeriesSort.cs +++ b/API/Extensions/QueryExtensions/Filtering/SeriesSort.cs @@ -31,7 +31,7 @@ public static class SeriesSort SortField.TimeToRead => query.DoOrderBy(s => s.AvgHoursToRead, sortOptions), SortField.ReleaseYear => query.DoOrderBy(s => s.Metadata.ReleaseYear, sortOptions), SortField.ReadProgress => query.DoOrderBy(s => s.Progress.Where(p => p.SeriesId == s.Id && p.AppUserId == userId) - .Select(p => p.LastModified) + .Select(p => p.LastModified) // TODO: Migrate this to UTC .Max(), sortOptions), SortField.AverageRating => query.DoOrderBy(s => s.ExternalSeriesMetadata.ExternalRatings .Where(p => p.SeriesId == s.Id).Average(p => p.AverageScore), sortOptions), diff --git a/API/Services/ReaderService.cs b/API/Services/ReaderService.cs index c25b7e327..945e55a45 100644 --- a/API/Services/ReaderService.cs +++ b/API/Services/ReaderService.cs @@ -134,7 +134,11 @@ public class ReaderService : IReaderService VolumeId = chapter.VolumeId, SeriesId = seriesId, ChapterId = chapter.Id, - LibraryId = series.LibraryId + LibraryId = series.LibraryId, + Created = DateTime.Now, + CreatedUtc = DateTime.UtcNow, + LastModified = DateTime.Now, + LastModifiedUtc = DateTime.UtcNow }); } else @@ -144,6 +148,8 @@ public class ReaderService : IReaderService userProgress.VolumeId = chapter.VolumeId; } + userProgress?.MarkModified(); + await _eventHub.SendMessageAsync(MessageFactory.UserProgressUpdate, MessageFactory.UserProgressUpdateEvent(user.Id, user.UserName!, seriesId, chapter.VolumeId, chapter.Id, chapter.Pages)); @@ -177,6 +183,7 @@ public class ReaderService : IReaderService userProgress.PagesRead = 0; userProgress.SeriesId = seriesId; userProgress.VolumeId = chapter.VolumeId; + userProgress.MarkModified(); await _eventHub.SendMessageAsync(MessageFactory.UserProgressUpdate, MessageFactory.UserProgressUpdateEvent(user.Id, user.UserName!, userProgress.SeriesId, userProgress.VolumeId, userProgress.ChapterId, 0)); @@ -266,7 +273,11 @@ public class ReaderService : IReaderService SeriesId = progressDto.SeriesId, ChapterId = progressDto.ChapterId, LibraryId = progressDto.LibraryId, - BookScrollId = progressDto.BookScrollId + BookScrollId = progressDto.BookScrollId, + Created = DateTime.Now, + CreatedUtc = DateTime.UtcNow, + LastModified = DateTime.Now, + LastModifiedUtc = DateTime.UtcNow }); _unitOfWork.UserRepository.Update(userWithProgress); } @@ -280,6 +291,8 @@ public class ReaderService : IReaderService _unitOfWork.AppUserProgressRepository.Update(userProgress); } + userProgress?.MarkModified(); + if (!_unitOfWork.HasChanges() || await _unitOfWork.CommitAsync()) { var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId); diff --git a/API/Services/Tasks/Scanner/Parser/BookParser.cs b/API/Services/Tasks/Scanner/Parser/BookParser.cs index e4b9a7ea6..616273554 100644 --- a/API/Services/Tasks/Scanner/Parser/BookParser.cs +++ b/API/Services/Tasks/Scanner/Parser/BookParser.cs @@ -31,7 +31,7 @@ public class BookParser(IDirectoryService directoryService, IBookService bookSer { var info2 = basicParser.Parse(filePath, rootPath, libraryRoot, LibraryType.Book, comicInfo); info.Merge(info2); - if (type == LibraryType.LightNovel && hasVolumeInSeries && info2 != null && Parser.ParseVolume(info2.Series) + if (hasVolumeInSeries && info2 != null && Parser.ParseVolume(info2.Series) .Equals(Parser.LooseLeafVolume)) { // Override the Series name so it groups appropriately diff --git a/API/Services/Tasks/Scanner/Parser/DefaultParser.cs b/API/Services/Tasks/Scanner/Parser/DefaultParser.cs index abf59f83a..4e7318caf 100644 --- a/API/Services/Tasks/Scanner/Parser/DefaultParser.cs +++ b/API/Services/Tasks/Scanner/Parser/DefaultParser.cs @@ -107,11 +107,11 @@ public abstract class DefaultParser(IDirectoryService directoryService) : IDefau { info.Volumes = info.ComicInfo.Volume; } - if (string.IsNullOrEmpty(info.Series) && !string.IsNullOrEmpty(info.ComicInfo.Series)) + if (!string.IsNullOrEmpty(info.ComicInfo.Series)) { info.Series = info.ComicInfo.Series.Trim(); } - if (string.IsNullOrEmpty(info.LocalizedSeries) && !string.IsNullOrEmpty(info.ComicInfo.LocalizedSeries)) + if (!string.IsNullOrEmpty(info.ComicInfo.LocalizedSeries)) { info.LocalizedSeries = info.ComicInfo.LocalizedSeries.Trim(); } diff --git a/API/Services/Tasks/Scanner/Parser/Parser.cs b/API/Services/Tasks/Scanner/Parser/Parser.cs index 29d2283d6..09ee87bab 100644 --- a/API/Services/Tasks/Scanner/Parser/Parser.cs +++ b/API/Services/Tasks/Scanner/Parser/Parser.cs @@ -232,7 +232,7 @@ public static class Parser RegexTimeout), // Gokukoku no Brynhildr - c001-008 (v01) [TrinityBAKumA], Black Bullet - v4 c17 [batoto] new Regex( - @"(?.*)( - )(?:v|vo|c|chapters)\d", + @"(?.+?)( - )(?:v|vo|c|chapters)\d", MatchOptions, RegexTimeout), // Kedouin Makoto - Corpse Party Musume, Chapter 19 [Dametrans].zip new Regex( diff --git a/API/Services/Tasks/Scanner/Parser/PdfParser.cs b/API/Services/Tasks/Scanner/Parser/PdfParser.cs index da71124a9..055fc1525 100644 --- a/API/Services/Tasks/Scanner/Parser/PdfParser.cs +++ b/API/Services/Tasks/Scanner/Parser/PdfParser.cs @@ -22,6 +22,11 @@ public class PdfParser(IDirectoryService directoryService) : DefaultParser(direc : Parser.ParseChapter(fileName) }; + if (type == LibraryType.Book) + { + ret.Chapters = Parser.DefaultChapter; + } + ret.Series = type == LibraryType.Comic ? Parser.ParseComicSeries(fileName) : Parser.ParseSeries(fileName); ret.Volumes = type == LibraryType.Comic ? Parser.ParseComicVolume(fileName) : Parser.ParseVolume(fileName); diff --git a/UI/Web/src/app/_pipes/filter.pipe.ts b/UI/Web/src/app/_pipes/filter.pipe.ts index 61890e6b2..d1fbaf239 100644 --- a/UI/Web/src/app/_pipes/filter.pipe.ts +++ b/UI/Web/src/app/_pipes/filter.pipe.ts @@ -14,6 +14,6 @@ export class FilterPipe implements PipeTransform { const ret = items.filter(item => callback(item)); if (ret.length === items.length) return items; // This will prevent a re-render return ret; -} + } } diff --git a/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.ts b/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.ts index cbf7f9938..9db9dfefa 100644 --- a/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.ts +++ b/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.ts @@ -339,6 +339,9 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked { get ShowStorylineTab() { if (this.libraryType === LibraryType.ComicVine) return false; + // Edge case for bad pdf parse + if (this.libraryType === LibraryType.Book && (this.volumes.length === 0 && this.chapters.length === 0 && this.storyChapters.length > 0)) return true; + return (this.libraryType !== LibraryType.Book && this.libraryType !== LibraryType.LightNovel && this.libraryType !== LibraryType.Comic) && (this.volumes.length > 0 || this.chapters.length > 0); } @@ -727,7 +730,12 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked { // Book libraries only have Volumes or Specials enabled if (this.libraryType === LibraryType.Book || this.libraryType === LibraryType.LightNovel) { if (this.volumes.length === 0) { - this.activeTabId = TabID.Specials; + if (this.specials.length === 0 && this.storyChapters.length > 0) { + // NOTE: This is an edge case caused by bad parsing of pdf files. Once the new pdf parser is in place, this should be removed + this.activeTabId = TabID.Storyline; + } else { + this.activeTabId = TabID.Specials; + } } else { this.activeTabId = TabID.Volumes; } diff --git a/UI/Web/src/app/sidenav/_modals/library-settings-modal/library-settings-modal.component.ts b/UI/Web/src/app/sidenav/_modals/library-settings-modal/library-settings-modal.component.ts index bdcd4a87d..e1e97304e 100644 --- a/UI/Web/src/app/sidenav/_modals/library-settings-modal/library-settings-modal.component.ts +++ b/UI/Web/src/app/sidenav/_modals/library-settings-modal/library-settings-modal.component.ts @@ -258,7 +258,10 @@ export class LibrarySettingsModalComponent implements OnInit { forceScan() { this.libraryService.scan(this.library!.id, true) - .subscribe(() => this.toastr.info(translate('toasts.forced-scan-queued', {name: this.library!.name}))); + .subscribe(() => { + this.toastr.info(translate('toasts.forced-scan-queued', {name: this.library!.name})); + this.close(); + }); } async save() { diff --git a/UI/Web/src/app/statistics/_components/kavitaplus-metadata-breakdown-stats/kavitaplus-metadata-breakdown-stats.component.html b/UI/Web/src/app/statistics/_components/kavitaplus-metadata-breakdown-stats/kavitaplus-metadata-breakdown-stats.component.html index ca85deb57..cf74d396b 100644 --- a/UI/Web/src/app/statistics/_components/kavitaplus-metadata-breakdown-stats/kavitaplus-metadata-breakdown-stats.component.html +++ b/UI/Web/src/app/statistics/_components/kavitaplus-metadata-breakdown-stats/kavitaplus-metadata-breakdown-stats.component.html @@ -1,37 +1,23 @@

{{t('title')}}

+

{{t('description')}}

@if(breakdown) { @if(breakdown.totalSeries === 0 || breakdown.seriesCompleted === 0) {
{{t('no-data')}}
- } - @if (percentDone >= 1) { -

{{t('complete') }}

} @else { -

{{t('total-series-progress-label', {percent: percentDone * 100 | percent}) }}

- -
- - - - - - - - - - - - - - -
{{t('completed-series-label')}} - {{ breakdown.seriesCompleted }} -
{{t('errored-series-label')}} - {{ breakdown.erroredSeries }} -
-
+ + + + + @if (breakdown.seriesCompleted >= breakdown.totalSeries) { +

{{t('complete') }} + @if (breakdown.erroredSeries > 0) { + {{t('errored-series-label') }} {{breakdown.erroredSeries}} + } +

+ } } }
diff --git a/UI/Web/src/app/statistics/_components/kavitaplus-metadata-breakdown-stats/kavitaplus-metadata-breakdown-stats.component.ts b/UI/Web/src/app/statistics/_components/kavitaplus-metadata-breakdown-stats/kavitaplus-metadata-breakdown-stats.component.ts index cb42975ee..bf3d41eac 100644 --- a/UI/Web/src/app/statistics/_components/kavitaplus-metadata-breakdown-stats/kavitaplus-metadata-breakdown-stats.component.ts +++ b/UI/Web/src/app/statistics/_components/kavitaplus-metadata-breakdown-stats/kavitaplus-metadata-breakdown-stats.component.ts @@ -3,13 +3,17 @@ import {StatisticsService} from "../../../_services/statistics.service"; import {KavitaPlusMetadataBreakdown} from "../../_models/kavitaplus-metadata-breakdown"; import {TranslocoDirective} from "@ngneat/transloco"; import {PercentPipe} from "@angular/common"; +import {NgbProgressbar, NgbProgressbarStacked, NgbTooltip} from "@ng-bootstrap/ng-bootstrap"; @Component({ selector: 'app-kavitaplus-metadata-breakdown-stats', standalone: true, imports: [ TranslocoDirective, - PercentPipe + PercentPipe, + NgbProgressbarStacked, + NgbProgressbar, + NgbTooltip ], templateUrl: './kavitaplus-metadata-breakdown-stats.component.html', styleUrl: './kavitaplus-metadata-breakdown-stats.component.scss', @@ -20,20 +24,18 @@ export class KavitaplusMetadataBreakdownStatsComponent { private readonly statsService = inject(StatisticsService); breakdown: KavitaPlusMetadataBreakdown | undefined; - completedStart!: number; - completedEnd!: number; - errorStart!: number; - errorEnd!: number; - percentDone!: number; + + errorPercent!: number; + completedPercent!: number; + constructor() { this.statsService.getKavitaPlusMetadataBreakdown().subscribe(res => { this.breakdown = res; - this.completedStart = 0; - this.completedEnd = ((res.seriesCompleted - res.erroredSeries) / res.totalSeries); - this.errorStart = this.completedEnd; - this.errorEnd = Math.max(1, ((res.seriesCompleted) / res.totalSeries)); - this.percentDone = res.seriesCompleted / res.totalSeries; + + this.errorPercent = (res.erroredSeries / res.totalSeries) * 100; + this.completedPercent = ((res.seriesCompleted - res.erroredSeries) / res.totalSeries) * 100; + this.cdRef.markForCheck(); }); } diff --git a/UI/Web/src/assets/langs/en.json b/UI/Web/src/assets/langs/en.json index 454d894f2..9142ac801 100644 --- a/UI/Web/src/assets/langs/en.json +++ b/UI/Web/src/assets/langs/en.json @@ -579,7 +579,7 @@ "published-label": "Published: ", "available": "Available", "description": "If you do not see an {{installed}}", - "description-continued": "tag, you are on a nightly release. Only major versions will show as available." + "description-continued": "tag, you are on a nightly release. Only major versions will show as available. A nightly tag will show when on a nightly from that stable." }, "invite-user": { @@ -1786,10 +1786,10 @@ "kavitaplus-metadata-breakdown-stats": { "title": "Kavita+ Metadata Breakdown", + "description": "Kavita fetches metadata (ratings, reviews, recommendations, etc) slowly over time for eligible series.", "no-data": "No data", "errored-series-label": "Errored Series", "completed-series-label": "Completed Series", - "total-series-progress-label": "Series Processed: {{percent}}", "complete": "All Series have metadata" }, diff --git a/openapi.json b/openapi.json index f573af266..9884a5f1e 100644 --- a/openapi.json +++ b/openapi.json @@ -7,7 +7,7 @@ "name": "GPL-3.0", "url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE" }, - "version": "0.7.14.8" + "version": "0.7.14.9" }, "servers": [ {