diff --git a/API.Tests/Services/SeriesServiceTests.cs b/API.Tests/Services/SeriesServiceTests.cs index 1ab48ed3e..1af97deb2 100644 --- a/API.Tests/Services/SeriesServiceTests.cs +++ b/API.Tests/Services/SeriesServiceTests.cs @@ -343,7 +343,7 @@ public class SeriesServiceTests : AbstractDbTest Assert.True(result); - var ratings = (await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings)) + var ratings = (await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings))! .Ratings; Assert.NotEmpty(ratings); Assert.Equal(3, ratings.First().Rating); @@ -780,7 +780,7 @@ public class SeriesServiceTests : AbstractDbTest { var series = CreateSeriesMock(); - var firstChapter = SeriesService.GetFirstChapterForMetadata(series, true); + var firstChapter = SeriesService.GetFirstChapterForMetadata(series); Assert.Same("1", firstChapter.Range); } @@ -789,7 +789,7 @@ public class SeriesServiceTests : AbstractDbTest { var series = CreateSeriesMock(); - var firstChapter = SeriesService.GetFirstChapterForMetadata(series, false); + var firstChapter = SeriesService.GetFirstChapterForMetadata(series); Assert.Same("1", firstChapter.Range); } @@ -808,10 +808,35 @@ public class SeriesServiceTests : AbstractDbTest new ChapterBuilder("1.2").WithFiles(files).WithPages(1).Build(), }; - var firstChapter = SeriesService.GetFirstChapterForMetadata(series, false); + var firstChapter = SeriesService.GetFirstChapterForMetadata(series); Assert.Same("1.1", firstChapter.Range); } + [Fact] + public void GetFirstChapterForMetadata_NonBook_ShouldReturnChapter1_WhenFirstVolumeIs3() + { + var file = new MangaFileBuilder("Test.cbz", MangaFormat.Archive, 1).Build(); + + var series = new SeriesBuilder("Test") + .WithVolume(new VolumeBuilder("0") + .WithChapter(new ChapterBuilder("1").WithPages(1).WithFile(file).Build()) + .WithChapter(new ChapterBuilder("2").WithPages(1).WithFile(file).Build()) + .Build()) + .WithVolume(new VolumeBuilder("2") + .WithChapter(new ChapterBuilder("21").WithPages(1).WithFile(file).Build()) + .WithChapter(new ChapterBuilder("22").WithPages(1).WithFile(file).Build()) + .Build()) + .WithVolume(new VolumeBuilder("3") + .WithChapter(new ChapterBuilder("31").WithPages(1).WithFile(file).Build()) + .WithChapter(new ChapterBuilder("32").WithPages(1).WithFile(file).Build()) + .Build()) + .Build(); + series.Library = new LibraryBuilder("Test LIb", LibraryType.Book).Build(); + + var firstChapter = SeriesService.GetFirstChapterForMetadata(series); + Assert.Same("1", firstChapter.Range); + } + #endregion #region SeriesRelation diff --git a/API/Controllers/LibraryController.cs b/API/Controllers/LibraryController.cs index e20349b49..ae569f272 100644 --- a/API/Controllers/LibraryController.cs +++ b/API/Controllers/LibraryController.cs @@ -50,22 +50,28 @@ public class LibraryController : BaseApiController /// /// Creates a new Library. Upon library creation, adds new library to all Admin accounts. /// - /// + /// /// [Authorize(Policy = "RequireAdminRole")] [HttpPost("create")] - public async Task AddLibrary(CreateLibraryDto createLibraryDto) + public async Task AddLibrary(UpdateLibraryDto dto) { - if (await _unitOfWork.LibraryRepository.LibraryExists(createLibraryDto.Name)) + if (await _unitOfWork.LibraryRepository.LibraryExists(dto.Name)) { return BadRequest("Library name already exists. Please choose a unique name to the server."); } var library = new Library { - Name = createLibraryDto.Name, - Type = createLibraryDto.Type, - Folders = createLibraryDto.Folders.Select(x => new FolderPath {Path = x}).ToList() + Name = dto.Name, + Type = dto.Type, + Folders = dto.Folders.Select(x => new FolderPath {Path = x}).Distinct().ToList(), + FolderWatching = dto.FolderWatching, + IncludeInDashboard = dto.IncludeInDashboard, + IncludeInRecommended = dto.IncludeInRecommended, + IncludeInSearch = dto.IncludeInSearch, + ManageCollections = dto.ManageCollections, + ManageReadingLists = dto.ManageReadingLists, }; _unitOfWork.LibraryRepository.Add(library); @@ -359,7 +365,7 @@ public class LibraryController : BaseApiController var originalFolders = library.Folders.Select(x => x.Path).ToList(); library.Name = newName; - library.Folders = dto.Folders.Select(s => new FolderPath() {Path = s}).ToList(); + library.Folders = dto.Folders.Select(s => new FolderPath() {Path = s}).Distinct().ToList(); var typeUpdate = library.Type != dto.Type; var folderWatchingUpdate = library.FolderWatching != dto.FolderWatching; diff --git a/API/Services/ReadingListService.cs b/API/Services/ReadingListService.cs index b987be1a1..fccb3d2cd 100644 --- a/API/Services/ReadingListService.cs +++ b/API/Services/ReadingListService.cs @@ -157,19 +157,19 @@ public class ReadingListService : IReadingListService readingList.CoverImageLocked = dto.CoverImageLocked; - if (NumberHelper.IsValidMonth(dto.StartingMonth)) + if (NumberHelper.IsValidMonth(dto.StartingMonth) || dto.StartingMonth == 0) { readingList.StartingMonth = dto.StartingMonth; } - if (NumberHelper.IsValidYear(dto.StartingYear)) + if (NumberHelper.IsValidYear(dto.StartingYear) || dto.StartingYear == 0) { readingList.StartingYear = dto.StartingYear; } - if (NumberHelper.IsValidMonth(dto.EndingMonth)) + if (NumberHelper.IsValidMonth(dto.EndingMonth) || dto.EndingMonth == 0) { readingList.EndingMonth = dto.EndingMonth; } - if (NumberHelper.IsValidYear(dto.EndingYear)) + if (NumberHelper.IsValidYear(dto.EndingYear) || dto.EndingYear == 0) { readingList.EndingYear = dto.EndingYear; } diff --git a/API/Services/SeriesService.cs b/API/Services/SeriesService.cs index 5be0ebfd4..f8616e651 100644 --- a/API/Services/SeriesService.cs +++ b/API/Services/SeriesService.cs @@ -48,14 +48,25 @@ public class SeriesService : ISeriesService /// /// Returns the first chapter for a series to extract metadata from (ie Summary, etc) /// - /// - /// + /// The full series with all volumes and chapters on it /// - public static Chapter? GetFirstChapterForMetadata(Series series, bool isBookLibrary) + public static Chapter? GetFirstChapterForMetadata(Series series) { - return series.Volumes.OrderBy(v => v.Number, ChapterSortComparer.Default) + var sortedVolumes = series.Volumes.OrderBy(v => v.Number, ChapterSortComparer.Default); + var minVolumeNumber = sortedVolumes + .Where(v => v.Number != 0) + .MinBy(v => v.Number); + + var minChapter = series.Volumes .SelectMany(v => v.Chapters.OrderBy(c => float.Parse(c.Number), ChapterSortComparer.Default)) .FirstOrDefault(); + + if (minVolumeNumber != null && minChapter != null && float.Parse(minChapter.Number) > minVolumeNumber.Number) + { + return minVolumeNumber.Chapters.MinBy(c => float.Parse(c.Number), ChapterSortComparer.Default); + } + + return minChapter; } public async Task UpdateSeriesMetadata(UpdateSeriesMetadataDto updateSeriesMetadataDto) diff --git a/API/Services/Tasks/Scanner/ProcessSeries.cs b/API/Services/Tasks/Scanner/ProcessSeries.cs index bedd27877..710d40a72 100644 --- a/API/Services/Tasks/Scanner/ProcessSeries.cs +++ b/API/Services/Tasks/Scanner/ProcessSeries.cs @@ -266,8 +266,7 @@ public class ProcessSeries : IProcessSeries public void UpdateSeriesMetadata(Series series, Library library) { series.Metadata ??= new SeriesMetadataBuilder().Build(); - var isBook = library.Type == LibraryType.Book; - var firstChapter = SeriesService.GetFirstChapterForMetadata(series, isBook); + var firstChapter = SeriesService.GetFirstChapterForMetadata(series); var firstFile = firstChapter?.Files.FirstOrDefault(); if (firstFile == null) return; diff --git a/UI/Web/src/_manga-reader-common.scss b/UI/Web/src/_manga-reader-common.scss index a7dfb4f19..123b925a6 100644 --- a/UI/Web/src/_manga-reader-common.scss +++ b/UI/Web/src/_manga-reader-common.scss @@ -15,7 +15,7 @@ img { &.full-height { height: calc(100vh); // We need to - $scrollbarHeight when there is a horizontal scroll on macos - display: flex; + display: flex; align-content: center; } @@ -42,13 +42,13 @@ img { } .full-width { - width: 100%; margin: 0 auto; vertical-align: top; max-width: fit-content; } .fit-to-screen.full-width { + width: 100%; max-height: calc(var(--vh)*100); } } diff --git a/UI/Web/src/app/reading-list/_components/reading-list-detail/reading-list-detail.component.ts b/UI/Web/src/app/reading-list/_components/reading-list-detail/reading-list-detail.component.ts index 7e50fcf1a..1c42011b2 100644 --- a/UI/Web/src/app/reading-list/_components/reading-list-detail/reading-list-detail.component.ts +++ b/UI/Web/src/app/reading-list/_components/reading-list-detail/reading-list-detail.component.ts @@ -49,7 +49,7 @@ export class ReadingListDetailComponent implements OnInit { constructor(private route: ActivatedRoute, private router: Router, private readingListService: ReadingListService, private actionService: ActionService, private actionFactoryService: ActionFactoryService, public utilityService: UtilityService, - public imageService: ImageService, private accountService: AccountService, private toastr: ToastrService, + public imageService: ImageService, private accountService: AccountService, private toastr: ToastrService, private confirmService: ConfirmService, private libraryService: LibraryService, private readerService: ReaderService, private readonly cdRef: ChangeDetectorRef) {} @@ -65,7 +65,7 @@ export class ReadingListDetailComponent implements OnInit { this.readingListImage = this.imageService.randomize(this.imageService.getReadingListCoverImage(this.listId)); forkJoin([ - this.libraryService.getLibraries(), + this.libraryService.getLibraries(), this.readingListService.getReadingList(this.listId) ]).subscribe(results => { const libraries = results[0]; @@ -90,7 +90,7 @@ export class ReadingListDetailComponent implements OnInit { if (user) { this.isAdmin = this.accountService.hasAdminRole(user); this.hasDownloadingRole = this.accountService.hasDownloadRole(user); - + this.actions = this.actionFactoryService.getReadingListActions(this.handleReadingListActionCallback.bind(this)) .filter(action => this.readingListService.actionListFilter(action, readingList, this.isAdmin)); this.cdRef.markForCheck(); @@ -123,18 +123,20 @@ export class ReadingListDetailComponent implements OnInit { this.router.navigate(this.readerService.getNavigationArray(item.libraryId, item.seriesId, item.chapterId, item.seriesFormat), {queryParams: params}); } - handleReadingListActionCallback(action: ActionItem, readingList: ReadingList) { + async handleReadingListActionCallback(action: ActionItem, readingList: ReadingList) { switch(action.action) { case Action.Delete: - this.deleteList(readingList); + await this.deleteList(readingList); break; case Action.Edit: this.actionService.editReadingList(readingList, (readingList: ReadingList) => { // Reload information around list - this.readingList = readingList; - this.readingListSummary = (this.readingList.summary === null ? '' : this.readingList.summary).replace(/\n/g, '
'); - this.readingListImage = this.imageService.randomize(this.imageService.getReadingListCoverImage(this.listId)); - this.cdRef.markForCheck(); + this.readingListService.getReadingList(this.listId).subscribe(rl => { + this.readingList = rl; + this.readingListSummary = (this.readingList.summary === null ? '' : this.readingList.summary).replace(/\n/g, '
'); + this.readingListImage = this.imageService.randomize(this.imageService.getReadingListCoverImage(this.listId)); + this.cdRef.markForCheck(); + }) }); break; } @@ -181,7 +183,7 @@ export class ReadingListDetailComponent implements OnInit { if (!this.readingList) return; const firstItem = this.items[0]; this.router.navigate( - this.readerService.getNavigationArray(firstItem.libraryId, firstItem.seriesId, firstItem.chapterId, firstItem.seriesFormat), + this.readerService.getNavigationArray(firstItem.libraryId, firstItem.seriesId, firstItem.chapterId, firstItem.seriesFormat), {queryParams: {readingListId: this.readingList.id, inconitoMode: inconitoMode}}); } @@ -198,7 +200,7 @@ export class ReadingListDetailComponent implements OnInit { } this.router.navigate( - this.readerService.getNavigationArray(currentlyReadingChapter.libraryId, currentlyReadingChapter.seriesId, currentlyReadingChapter.chapterId, currentlyReadingChapter.seriesFormat), + this.readerService.getNavigationArray(currentlyReadingChapter.libraryId, currentlyReadingChapter.seriesId, currentlyReadingChapter.chapterId, currentlyReadingChapter.seriesFormat), {queryParams: {readingListId: this.readingList.id, inconitoMode: inconitoMode}}); } diff --git a/openapi.json b/openapi.json index 3ac94ed06..ac2a6cea6 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.2.26" + "version": "0.7.2.28" }, "servers": [ { @@ -2129,17 +2129,17 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CreateLibraryDto" + "$ref": "#/components/schemas/UpdateLibraryDto" } }, "text/json": { "schema": { - "$ref": "#/components/schemas/CreateLibraryDto" + "$ref": "#/components/schemas/UpdateLibraryDto" } }, "application/*+json": { "schema": { - "$ref": "#/components/schemas/CreateLibraryDto" + "$ref": "#/components/schemas/UpdateLibraryDto" } } } @@ -11191,31 +11191,6 @@ }, "additionalProperties": false }, - "CreateLibraryDto": { - "required": [ - "folders", - "name", - "type" - ], - "type": "object", - "properties": { - "name": { - "minLength": 1, - "type": "string" - }, - "type": { - "$ref": "#/components/schemas/LibraryType" - }, - "folders": { - "minItems": 1, - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - }, "CreateReadingListDto": { "type": "object", "properties": {