diff --git a/API/Controllers/OPDSController.cs b/API/Controllers/OPDSController.cs index b025be4a4..3466b5587 100644 --- a/API/Controllers/OPDSController.cs +++ b/API/Controllers/OPDSController.cs @@ -549,12 +549,15 @@ public async Task GetReadingLists(string apiKey, [FromQuery] int Id = readingListDto.Id.ToString(), Title = readingListDto.Title, Summary = readingListDto.Summary, - Links = new List() - { - CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}/reading-list/{readingListDto.Id}"), - CreateLink(FeedLinkRelation.Image, FeedLinkType.Image, $"{baseUrl}api/image/readinglist-cover?readingListId={readingListDto.Id}&apiKey={apiKey}"), - CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image, $"{baseUrl}api/image/readinglist-cover?readingListId={readingListDto.Id}&apiKey={apiKey}") - } + Links = + [ + CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, + $"{prefix}{apiKey}/reading-list/{readingListDto.Id}"), + CreateLink(FeedLinkRelation.Image, FeedLinkType.Image, + $"{baseUrl}api/image/readinglist-cover?readingListId={readingListDto.Id}&apiKey={apiKey}"), + CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image, + $"{baseUrl}api/image/readinglist-cover?readingListId={readingListDto.Id}&apiKey={apiKey}") + ] }); } @@ -573,7 +576,7 @@ private static UserParams GetUserParams(int pageNumber) [HttpGet("{apiKey}/reading-list/{readingListId}")] [Produces("application/xml")] - public async Task GetReadingListItems(int readingListId, string apiKey) + public async Task GetReadingListItems(int readingListId, string apiKey, [FromQuery] int pageNumber = 0) { var userId = await GetUser(apiKey); if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds) @@ -592,7 +595,7 @@ public async Task GetReadingListItems(int readingListId, string a var feed = CreateFeed(readingList.Title + " " + await _localizationService.Translate(userId, "reading-list"), $"{apiKey}/reading-list/{readingListId}", apiKey, prefix); SetFeedId(feed, $"reading-list-{readingListId}"); - var items = (await _unitOfWork.ReadingListRepository.GetReadingListItemDtosByIdAsync(readingListId, userId)).ToList(); + var items = await _unitOfWork.ReadingListRepository.GetReadingListItemDtosByIdAsync(readingListId, userId); foreach (var item in items) { var chapterDto = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(item.ChapterId); diff --git a/API/Controllers/SettingsController.cs b/API/Controllers/SettingsController.cs index 5d0c207d1..d884d05c8 100644 --- a/API/Controllers/SettingsController.cs +++ b/API/Controllers/SettingsController.cs @@ -345,7 +345,7 @@ public async Task> UpdateSettings(ServerSettingDt if (updateBookmarks) { - BackgroundJob.Enqueue(() => UpdateBookmarkDirectory(originalBookmarkDirectory, bookmarkDirectory)); + UpdateBookmarkDirectory(originalBookmarkDirectory, bookmarkDirectory); } if (updateSettingsDto.EnableFolderWatching) @@ -371,6 +371,7 @@ public async Task> UpdateSettings(ServerSettingDt return Ok(updateSettingsDto); } + private void UpdateBookmarkDirectory(string originalBookmarkDirectory, string bookmarkDirectory) { _directoryService.ExistOrCreate(bookmarkDirectory); diff --git a/API/DTOs/Account/ConfirmEmailDto.cs b/API/DTOs/Account/ConfirmEmailDto.cs index fb9a7c470..2f5849e74 100644 --- a/API/DTOs/Account/ConfirmEmailDto.cs +++ b/API/DTOs/Account/ConfirmEmailDto.cs @@ -9,7 +9,7 @@ public class ConfirmEmailDto [Required] public string Token { get; set; } = default!; [Required] - [StringLength(32, MinimumLength = 6)] + [StringLength(256, MinimumLength = 6)] public string Password { get; set; } = default!; [Required] public string Username { get; set; } = default!; diff --git a/API/DTOs/Account/ConfirmPasswordResetDto.cs b/API/DTOs/Account/ConfirmPasswordResetDto.cs index 862a18986..16dd86f9a 100644 --- a/API/DTOs/Account/ConfirmPasswordResetDto.cs +++ b/API/DTOs/Account/ConfirmPasswordResetDto.cs @@ -9,6 +9,6 @@ public class ConfirmPasswordResetDto [Required] public string Token { get; set; } = default!; [Required] - [StringLength(32, MinimumLength = 6)] + [StringLength(256, MinimumLength = 6)] public string Password { get; set; } = default!; } diff --git a/API/DTOs/Account/ResetPasswordDto.cs b/API/DTOs/Account/ResetPasswordDto.cs index fc7147f62..51a195131 100644 --- a/API/DTOs/Account/ResetPasswordDto.cs +++ b/API/DTOs/Account/ResetPasswordDto.cs @@ -13,7 +13,7 @@ public class ResetPasswordDto /// The new password /// [Required] - [StringLength(32, MinimumLength = 6)] + [StringLength(256, MinimumLength = 6)] public string Password { get; init; } = default!; /// /// The old, existing password. If an admin is performing the change, this is not required. Otherwise, it is. diff --git a/API/DTOs/RegisterDto.cs b/API/DTOs/RegisterDto.cs index b6132046f..d0118e385 100644 --- a/API/DTOs/RegisterDto.cs +++ b/API/DTOs/RegisterDto.cs @@ -11,6 +11,6 @@ public class RegisterDto /// public string Email { get; init; } = default!; [Required] - [StringLength(32, MinimumLength = 6)] + [StringLength(256, MinimumLength = 6)] public string Password { get; set; } = default!; } diff --git a/API/Services/CacheService.cs b/API/Services/CacheService.cs index c6e539348..4f5dadfe0 100644 --- a/API/Services/CacheService.cs +++ b/API/Services/CacheService.cs @@ -119,6 +119,7 @@ public IEnumerable GetCachedFileDimensions(string cachePath) } _logger.LogDebug("File Dimensions call for {Length} images took {Time}ms", dimensions.Count, sw.ElapsedMilliseconds); + return dimensions; } diff --git a/Kavita.Common/Kavita.Common.csproj b/Kavita.Common/Kavita.Common.csproj index 369647356..2f144e242 100644 --- a/Kavita.Common/Kavita.Common.csproj +++ b/Kavita.Common/Kavita.Common.csproj @@ -3,7 +3,7 @@ net8.0 kavitareader.com Kavita - 0.8.3.1 + 0.8.3.2 en true @@ -20,4 +20,4 @@ - \ No newline at end of file + diff --git a/UI/Web/src/_card-item-common.scss b/UI/Web/src/_card-item-common.scss index 29924c495..f187660fd 100644 --- a/UI/Web/src/_card-item-common.scss +++ b/UI/Web/src/_card-item-common.scss @@ -168,7 +168,7 @@ $image-width: 160px; font-size: 0.8rem; margin: 0; text-align: center; - max-width: 110px; + max-width: 98px; a { overflow: hidden; diff --git a/UI/Web/src/app/_single-module/card-actionables/card-actionables.component.scss b/UI/Web/src/app/_single-module/card-actionables/card-actionables.component.scss index 34ff10fe6..6f1d105e0 100644 --- a/UI/Web/src/app/_single-module/card-actionables/card-actionables.component.scss +++ b/UI/Web/src/app/_single-module/card-actionables/card-actionables.component.scss @@ -27,6 +27,10 @@ padding: var(--bs-dropdown-item-padding-y) 0; } +.btn { + padding: 5px; +} + // Robbie added this but it broke most of the uses //.dropdown-toggle { // padding-top: 0; diff --git a/UI/Web/src/app/_single-module/details-tab/details-tab.component.html b/UI/Web/src/app/_single-module/details-tab/details-tab.component.html index 5f96f206c..5ce715b6f 100644 --- a/UI/Web/src/app/_single-module/details-tab/details-tab.component.html +++ b/UI/Web/src/app/_single-module/details-tab/details-tab.component.html @@ -1,21 +1,25 @@
-
+

{{t('genres-title')}}

- - - {{item.title}} - - +
+ + + {{item.title}} + + +
-
+

{{t('tags-title')}}

- - - {{item.title}} - - +
+ + + {{item.title}} + + +
@@ -29,7 +33,7 @@

{{t('tags-title')}}

- @if (genres.length > 0 || tags.length > 0) { + @if (genres.length > 0 || tags.length > 0 || webLinks.length > 0) { } diff --git a/UI/Web/src/app/admin/manage-email-settings/manage-email-settings.component.html b/UI/Web/src/app/admin/manage-email-settings/manage-email-settings.component.html index 33482af62..bef6f63c2 100644 --- a/UI/Web/src/app/admin/manage-email-settings/manage-email-settings.component.html +++ b/UI/Web/src/app/admin/manage-email-settings/manage-email-settings.component.html @@ -2,11 +2,7 @@

{{t('description')}}

-

{{t('setting-description')}}

- - @if (settingsForm.dirty) { -
{{t('test-warning')}}
- } +

{{t('setting-description')}} {{t('test-warning')}}

@if (settingsForm.get('hostName'); as formControl) { diff --git a/UI/Web/src/app/admin/manage-email-settings/manage-email-settings.component.ts b/UI/Web/src/app/admin/manage-email-settings/manage-email-settings.component.ts index f3b872c36..5c05e2d5e 100644 --- a/UI/Web/src/app/admin/manage-email-settings/manage-email-settings.component.ts +++ b/UI/Web/src/app/admin/manage-email-settings/manage-email-settings.component.ts @@ -1,14 +1,14 @@ import {ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, inject, OnInit} from '@angular/core'; import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms'; import {ToastrService} from 'ngx-toastr'; -import {debounceTime, distinctUntilChanged, filter, switchMap, take, tap} from 'rxjs'; +import {debounceTime, distinctUntilChanged, filter, map, switchMap, take, tap} from 'rxjs'; import {SettingsService} from '../settings.service'; import {ServerSettings} from '../_models/server-settings'; import { NgbAlert, NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; -import {NgIf, NgTemplateOutlet, TitleCasePipe} from '@angular/common'; +import {AsyncPipe, NgIf, NgTemplateOutlet, TitleCasePipe} from '@angular/common'; import {translate, TranslocoModule} from "@jsverse/transloco"; import {SafeHtmlPipe} from "../../_pipes/safe-html.pipe"; import {ManageMediaIssuesComponent} from "../manage-media-issues/manage-media-issues.component"; @@ -25,7 +25,7 @@ import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgIf, ReactiveFormsModule, NgbTooltip, NgTemplateOutlet, TranslocoModule, SafeHtmlPipe, - ManageMediaIssuesComponent, TitleCasePipe, NgbAlert, SettingItemComponent, SettingSwitchComponent, DefaultValuePipe, BytesPipe] + ManageMediaIssuesComponent, TitleCasePipe, NgbAlert, SettingItemComponent, SettingSwitchComponent, DefaultValuePipe, BytesPipe, AsyncPipe] }) export class ManageEmailSettingsComponent implements OnInit { diff --git a/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.ts b/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.ts index dc4df9825..6fe077012 100644 --- a/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.ts +++ b/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.ts @@ -21,7 +21,7 @@ import {allEncodeFormats} from '../_models/encode-format'; import {ManageMediaIssuesComponent} from '../manage-media-issues/manage-media-issues.component'; import {NgFor, NgIf, NgTemplateOutlet} from '@angular/common'; import {translate, TranslocoDirective, TranslocoService} from "@jsverse/transloco"; -import {allCoverImageSizes} from '../_models/cover-image-size'; +import {allCoverImageSizes, CoverImageSize} from '../_models/cover-image-size'; import {pageLayoutModes} from "../../_models/preferences/preferences"; import {PageLayoutModePipe} from "../../_pipes/page-layout-mode.pipe"; import {SettingItemComponent} from "../../settings/_components/setting-item/setting-item.component"; @@ -62,7 +62,7 @@ export class ManageMediaSettingsComponent implements OnInit { this.serverSettings = settings; this.settingsForm.addControl('encodeMediaAs', new FormControl(this.serverSettings.encodeMediaAs, [Validators.required])); this.settingsForm.addControl('bookmarksDirectory', new FormControl(this.serverSettings.bookmarksDirectory, [Validators.required])); - this.settingsForm.addControl('coverImageSize', new FormControl(this.serverSettings.coverImageSize, [Validators.required])); + this.settingsForm.addControl('coverImageSize', new FormControl(this.serverSettings.coverImageSize || CoverImageSize.Default, [Validators.required])); // Automatically save settings as we edit them this.settingsForm.valueChanges.pipe( diff --git a/UI/Web/src/app/admin/manage-tasks-settings/manage-tasks-settings.component.ts b/UI/Web/src/app/admin/manage-tasks-settings/manage-tasks-settings.component.ts index 02603d510..009a2840a 100644 --- a/UI/Web/src/app/admin/manage-tasks-settings/manage-tasks-settings.component.ts +++ b/UI/Web/src/app/admin/manage-tasks-settings/manage-tasks-settings.component.ts @@ -107,7 +107,7 @@ export class ManageTasksSettingsComponent implements OnInit { }, { name: 'sync-themes-task', - description: 'sync-themes-desc', + description: 'sync-themes-task-desc', api: this.serverService.syncThemes(), successMessage: 'sync-themes-success' }, @@ -143,72 +143,20 @@ export class ManageTasksSettingsComponent implements OnInit { this.logLevels = result.levels; this.serverSettings = result.settings; + // Create base controls for taskScan, taskBackup, taskCleanup this.settingsForm.addControl('taskScan', new FormControl(this.serverSettings.taskScan, [Validators.required])); this.settingsForm.addControl('taskBackup', new FormControl(this.serverSettings.taskBackup, [Validators.required])); this.settingsForm.addControl('taskCleanup', new FormControl(this.serverSettings.taskCleanup, [Validators.required])); - if (!this.taskFrequencies.includes(this.serverSettings.taskScan)) { - this.settingsForm.get('taskScan')?.setValue(this.customOption); - this.settingsForm.addControl('taskScanCustom', new FormControl(this.serverSettings.taskScan, [Validators.required])); - } else { - this.settingsForm.addControl('taskScanCustom', new FormControl('', [Validators.required])); - } - - if (!this.taskFrequencies.includes(this.serverSettings.taskBackup)) { - this.settingsForm.get('taskBackup')?.setValue(this.customOption); - this.settingsForm.addControl('taskBackupCustom', new FormControl(this.serverSettings.taskBackup, [Validators.required])); - } else { - this.settingsForm.addControl('taskBackupCustom', new FormControl('', [Validators.required])); - } - if (!this.taskFrequenciesForCleanup.includes(this.serverSettings.taskCleanup)) { - this.settingsForm.get('taskCleanup')?.setValue(this.customOption); - this.settingsForm.addControl('taskCleanupCustom', new FormControl(this.serverSettings.taskCleanup, [Validators.required])); - } else { - this.settingsForm.addControl('taskCleanupCustom', new FormControl('', [Validators.required])); - } - - this.settingsForm.get('taskScanCustom')?.valueChanges.pipe( - debounceTime(100), - switchMap(val => this.settingsService.isValidCronExpression(val)), - tap(isValid => { - if (isValid) { - this.settingsForm.get('taskScanCustom')?.setErrors(null); - } else { - this.settingsForm.get('taskScanCustom')?.setErrors({invalidCron: true}) - } - this.cdRef.markForCheck(); - }), - takeUntilDestroyed(this.destroyRef) - ).subscribe(); - - this.settingsForm.get('taskBackupCustom')?.valueChanges.pipe( - debounceTime(100), - switchMap(val => this.settingsService.isValidCronExpression(val)), - tap(isValid => { - if (isValid) { - this.settingsForm.get('taskBackupCustom')?.setErrors(null); - } else { - this.settingsForm.get('taskBackupCustom')?.setErrors({invalidCron: true}) - } - this.cdRef.markForCheck(); - }), - takeUntilDestroyed(this.destroyRef) - ).subscribe(); + this.updateCustomFields('taskScan', 'taskScanCustom', this.taskFrequencies, this.serverSettings.taskScan); + this.updateCustomFields('taskBackup', 'taskBackupCustom', this.taskFrequencies, this.serverSettings.taskBackup); + this.updateCustomFields('taskCleanup', 'taskCleanupCustom', this.taskFrequenciesForCleanup, this.serverSettings.taskCleanup); - this.settingsForm.get('taskCleanupCustom')?.valueChanges.pipe( - debounceTime(100), - switchMap(val => this.settingsService.isValidCronExpression(val)), - tap(isValid => { - if (isValid) { - this.settingsForm.get('taskCleanupCustom')?.setErrors(null); - } else { - this.settingsForm.get('taskCleanupCustom')?.setErrors({invalidCron: true}) - } - this.cdRef.markForCheck(); - }), - takeUntilDestroyed(this.destroyRef) - ).subscribe(); + // Call the validation method for each custom control + this.validateCronExpression('taskScanCustom'); + this.validateCronExpression('taskBackupCustom'); + this.validateCronExpression('taskCleanupCustom'); // Automatically save settings as we edit them this.settingsForm.valueChanges.pipe( @@ -235,6 +183,35 @@ export class ManageTasksSettingsComponent implements OnInit { this.cdRef.markForCheck(); } + // Custom logic to dynamically handle custom fields and validators + updateCustomFields(controlName: string, customControlName: string, frequencyList: string[], currentSetting: string) { + if (!frequencyList.includes(currentSetting)) { + // If the setting is not in the predefined list, it's a custom value + this.settingsForm.get(controlName)?.setValue(this.customOption); + this.settingsForm.addControl(customControlName, new FormControl(currentSetting, [Validators.required])); + } else { + // Otherwise, reset the custom control (no need for Validators.required here) + this.settingsForm.addControl(customControlName, new FormControl('')); + } + } + + // Validate the custom fields for cron expressions + validateCronExpression(controlName: string) { + this.settingsForm.get(controlName)?.valueChanges.pipe( + debounceTime(100), + switchMap(val => this.settingsService.isValidCronExpression(val)), + tap(isValid => { + if (isValid) { + this.settingsForm.get(controlName)?.setErrors(null); + } else { + this.settingsForm.get(controlName)?.setErrors({ invalidCron: true }); + } + this.cdRef.markForCheck(); + }), + takeUntilDestroyed(this.destroyRef) + ).subscribe(); + } + resetForm() { this.settingsForm.get('taskScan')?.setValue(this.serverSettings.taskScan, {onlySelf: true, emitEvent: false}); diff --git a/UI/Web/src/app/all-series/_components/all-series/all-series.component.ts b/UI/Web/src/app/all-series/_components/all-series/all-series.component.ts index a13b3577f..a1e3e4900 100644 --- a/UI/Web/src/app/all-series/_components/all-series/all-series.component.ts +++ b/UI/Web/src/app/all-series/_components/all-series/all-series.component.ts @@ -112,6 +112,7 @@ export class AllSeriesComponent implements OnInit { private readonly cdRef: ChangeDetectorRef) { this.router.routeReuseStrategy.shouldReuseRoute = () => false; + console.log('url: ', this.route.snapshot); this.filterUtilityService.filterPresetsFromUrl(this.route.snapshot).subscribe(filter => { this.filter = filter; diff --git a/UI/Web/src/app/app.component.ts b/UI/Web/src/app/app.component.ts index aac0cb8e1..c74c9f0fc 100644 --- a/UI/Web/src/app/app.component.ts +++ b/UI/Web/src/app/app.component.ts @@ -22,7 +22,7 @@ import {ServerService} from "./_services/server.service"; import {OutOfDateModalComponent} from "./announcements/_components/out-of-date-modal/out-of-date-modal.component"; import {PreferenceNavComponent} from "./sidenav/preference-nav/preference-nav.component"; import {Breakpoint, UtilityService} from "./shared/_services/utility.service"; -import {translate} from "@jsverse/transloco"; +import {TranslocoService} from "@jsverse/transloco"; @Component({ selector: 'app-root', @@ -47,6 +47,7 @@ export class AppComponent implements OnInit { private readonly router = inject(Router); private readonly themeService = inject(ThemeService); private readonly document = inject(DOCUMENT); + private readonly translocoService = inject(TranslocoService); protected readonly Breakpoint = Breakpoint; @@ -126,6 +127,9 @@ export class AppComponent implements OnInit { // Bust locale cache localStorage.removeItem('@transloco/translations/timestamp'); localStorage.removeItem('@transloco/translations'); + (this.translocoService as any).cache.delete(localStorage.getItem('kavita-locale') || 'en'); + (this.translocoService as any).cache.clear(); + localStorage.setItem('kavita--version', version); location.reload(); } localStorage.setItem('kavita--version', version); diff --git a/UI/Web/src/app/cards/chapter-card/chapter-card.component.html b/UI/Web/src/app/cards/chapter-card/chapter-card.component.html index c39ce980c..9268309e3 100644 --- a/UI/Web/src/app/cards/chapter-card/chapter-card.component.html +++ b/UI/Web/src/app/cards/chapter-card/chapter-card.component.html @@ -80,7 +80,7 @@ @if (chapter.isSpecial) { {{chapter.title || chapter.range}} } @else { - + } diff --git a/UI/Web/src/app/cards/entity-title/entity-title.component.html b/UI/Web/src/app/cards/entity-title/entity-title.component.html index e0fc0471d..7817d870c 100644 --- a/UI/Web/src/app/cards/entity-title/entity-title.component.html +++ b/UI/Web/src/app/cards/entity-title/entity-title.component.html @@ -33,31 +33,50 @@ @case (LibraryType.Manga) { @if (titleName !== '' && prioritizeTitleName) { @if (isChapter && includeChapter) { - {{t('chapter') + ' ' + number + ' - ' }} + @if (number === LooseLeafOrSpecial) { + {{t('chapter') + ' - ' }} + } @else { + {{t('chapter') + ' ' + number + ' - ' }} + } + } {{titleName}} } @else { @if (includeVolume && volumeTitle !== '') { - {{number !== LooseLeafOrSpecial ? (isChapter && includeVolume ? volumeTitle : '') : ''}} + @if (number !== LooseLeafOrSpecial && isChapter && includeVolume) { + {{volumeTitle}} + } } - {{number !== LooseLeafOrSpecial ? (isChapter ? (t('chapter') + ' ') + number : volumeTitle) : t('special')}} + @if (number !== LooseLeafOrSpecial) { + @if (isChapter) { + {{t('chapter') + ' ' + number}} + } @else { + {{volumeTitle}} + } + } @else { + {{t('special')}} + } } } @case (LibraryType.Book) { @if (titleName !== '' && prioritizeTitleName) { {{titleName}} + } @else if (number === LooseLeafOrSpecial) { + {{null | defaultValue}} } @else { - {{volumeTitle}} + {{t('book-num', {num: volumeTitle})}} } } @case (LibraryType.LightNovel) { @if (titleName !== '' && prioritizeTitleName) { {{titleName}} + } @else if (number === LooseLeafOrSpecial) { + {{null | defaultValue}} } @else { - {{volumeTitle}} + {{t('book-num', {num: (isChapter ? number : volumeTitle)})}} } } diff --git a/UI/Web/src/app/cards/entity-title/entity-title.component.ts b/UI/Web/src/app/cards/entity-title/entity-title.component.ts index 1b62fe08d..eafdfdef4 100644 --- a/UI/Web/src/app/cards/entity-title/entity-title.component.ts +++ b/UI/Web/src/app/cards/entity-title/entity-title.component.ts @@ -4,6 +4,7 @@ import { Chapter, LooseLeafOrDefaultNumber } from 'src/app/_models/chapter'; import { LibraryType } from 'src/app/_models/library/library'; import { Volume } from 'src/app/_models/volume'; import {TranslocoModule} from "@jsverse/transloco"; +import {DefaultValuePipe} from "../../_pipes/default-value.pipe"; /** * This is primarily used for list item @@ -12,7 +13,8 @@ import {TranslocoModule} from "@jsverse/transloco"; selector: 'app-entity-title', standalone: true, imports: [ - TranslocoModule + TranslocoModule, + DefaultValuePipe ], templateUrl: './entity-title.component.html', styleUrls: ['./entity-title.component.scss'], @@ -20,7 +22,6 @@ import {TranslocoModule} from "@jsverse/transloco"; }) export class EntityTitleComponent implements OnInit { - protected readonly LooseLeafOrSpecialNumber = LooseLeafOrDefaultNumber; protected readonly LooseLeafOrSpecial = LooseLeafOrDefaultNumber + ""; protected readonly LibraryType = LibraryType; @@ -59,6 +60,7 @@ export class EntityTitleComponent implements OnInit { this.volumeTitle = c.volumeTitle || ''; this.titleName = c.titleName || ''; this.number = c.range; + } else { const v = this.utilityService.asVolume(this.entity); this.volumeTitle = v.name || ''; diff --git a/UI/Web/src/app/chapter-detail/chapter-detail.component.ts b/UI/Web/src/app/chapter-detail/chapter-detail.component.ts index ad64e2063..fd40ba436 100644 --- a/UI/Web/src/app/chapter-detail/chapter-detail.component.ts +++ b/UI/Web/src/app/chapter-detail/chapter-detail.component.ts @@ -9,7 +9,7 @@ import { } from '@angular/core'; import {BulkOperationsComponent} from "../cards/bulk-operations/bulk-operations.component"; import {TagBadgeComponent} from "../shared/tag-badge/tag-badge.component"; -import {AsyncPipe, DecimalPipe, DOCUMENT, NgStyle, NgClass, DatePipe} from "@angular/common"; +import {AsyncPipe, DecimalPipe, DOCUMENT, NgStyle, NgClass, DatePipe, Location} from "@angular/common"; import {CardActionablesComponent} from "../_single-module/card-actionables/card-actionables.component"; import {CarouselReelComponent} from "../carousel/_components/carousel-reel/carousel-reel.component"; import {ExternalSeriesCardComponent} from "../cards/external-series-card/external-series-card.component"; @@ -171,6 +171,7 @@ export class ChapterDetailComponent implements OnInit { private readonly messageHub = inject(MessageHubService); private readonly actionFactoryService = inject(ActionFactoryService); private readonly actionService = inject(ActionService); + private readonly location = inject(Location); protected readonly AgeRating = AgeRating; protected readonly TabID = TabID; @@ -331,8 +332,9 @@ export class ChapterDetailComponent implements OnInit { } updateUrl(activeTab: TabID) { - const newUrl = `${this.router.url.split('#')[0]}#${activeTab}`; - window.history.replaceState({}, '', newUrl); + const tokens = this.location.path().split('#'); + const newUrl = `${tokens[0]}#${activeTab}`; + this.location.replaceState(newUrl) } openPerson(field: FilterField, value: number) { 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 a7815f72a..125f7ba9d 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 @@ -1,7 +1,7 @@ import { AsyncPipe, DecimalPipe, - DOCUMENT, JsonPipe, + DOCUMENT, JsonPipe, Location, NgClass, NgOptimizedImage, NgStyle, @@ -211,6 +211,7 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked { protected readonly themeService = inject(ThemeService); private readonly filterUtilityService = inject(FilterUtilitiesService); private readonly scrobbleService = inject(ScrobblingService); + private readonly location = inject(Location); protected readonly LibraryType = LibraryType; protected readonly TabID = TabID; @@ -523,6 +524,8 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked { this.cdRef.markForCheck(); }); + + this.route.fragment.pipe(tap(frag => { if (frag !== null && this.activeTabId !== (frag as TabID)) { this.activeTabId = frag as TabID; @@ -561,9 +564,9 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked { } updateUrl(activeTab: TabID) { - var tokens = this.router.url.split('#'); + const tokens = this.location.path().split('#'); const newUrl = `${tokens[0]}#${activeTab}`; - window.history.replaceState({}, '', newUrl); + this.location.replaceState(newUrl) } handleSeriesActionCallback(action: ActionItem, series: Series) { diff --git a/UI/Web/src/app/settings/_components/setting-item/setting-item.component.ts b/UI/Web/src/app/settings/_components/setting-item/setting-item.component.ts index 7a50d98e6..36b84ab31 100644 --- a/UI/Web/src/app/settings/_components/setting-item/setting-item.component.ts +++ b/UI/Web/src/app/settings/_components/setting-item/setting-item.component.ts @@ -69,7 +69,9 @@ export class SettingItemComponent { if (!this.toggleOnViewClick) return false; const mouseEvent = event as MouseEvent; - return !elementRef.nativeElement.contains(mouseEvent.target) + const selection = window.getSelection(); + const hasSelection = selection !== null && selection.toString().trim() === ''; + return !elementRef.nativeElement.contains(mouseEvent.target) && hasSelection; }), tap(() => { this.isEditMode = false; diff --git a/UI/Web/src/app/shared/_services/filter-utilities.service.ts b/UI/Web/src/app/shared/_services/filter-utilities.service.ts index b266ec00c..1cb7c70c1 100644 --- a/UI/Web/src/app/shared/_services/filter-utilities.service.ts +++ b/UI/Web/src/app/shared/_services/filter-utilities.service.ts @@ -1,4 +1,4 @@ -import {Injectable} from '@angular/core'; +import {inject, Injectable} from '@angular/core'; import {ActivatedRouteSnapshot, Params, Router} from '@angular/router'; import {SortField, SortOptions} from 'src/app/_models/metadata/series-filter'; import {MetadataService} from "../../_services/metadata.service"; @@ -12,6 +12,7 @@ import {TextResonse} from "../../_types/text-response"; import {environment} from "../../../environments/environment"; import {map, tap} from "rxjs/operators"; import {of, switchMap} from "rxjs"; +import {Location} from "@angular/common"; @Injectable({ @@ -19,9 +20,12 @@ import {of, switchMap} from "rxjs"; }) export class FilterUtilitiesService { - private apiUrl = environment.apiUrl; + private readonly location = inject(Location); + private readonly router = inject(Router); + private readonly metadataService = inject(MetadataService); + private readonly http = inject(HttpClient); - constructor(private metadataService: MetadataService, private router: Router, private http: HttpClient) {} + private apiUrl = environment.apiUrl; encodeFilter(filter: SeriesFilterV2 | undefined) { return this.http.post(this.apiUrl + 'filter/encode', filter, TextResonse); diff --git a/UI/Web/src/app/sidenav/_components/dashboard-stream-list-item/dashboard-stream-list-item.component.html b/UI/Web/src/app/sidenav/_components/dashboard-stream-list-item/dashboard-stream-list-item.component.html index 9bf846f33..2b5c173ba 100644 --- a/UI/Web/src/app/sidenav/_components/dashboard-stream-list-item/dashboard-stream-list-item.component.html +++ b/UI/Web/src/app/sidenav/_components/dashboard-stream-list-item/dashboard-stream-list-item.component.html @@ -22,7 +22,7 @@
@if (!item.isProvided) { }
diff --git a/UI/Web/src/app/sidenav/_components/dashboard-stream-list-item/dashboard-stream-list-item.component.ts b/UI/Web/src/app/sidenav/_components/dashboard-stream-list-item/dashboard-stream-list-item.component.ts index 17c1f2704..8842c13a8 100644 --- a/UI/Web/src/app/sidenav/_components/dashboard-stream-list-item/dashboard-stream-list-item.component.ts +++ b/UI/Web/src/app/sidenav/_components/dashboard-stream-list-item/dashboard-stream-list-item.component.ts @@ -1,11 +1,11 @@ import { ChangeDetectionStrategy, Component, - EventEmitter, + EventEmitter, inject, Input, Output } from '@angular/core'; -import {CommonModule, NgClass} from '@angular/common'; +import {APP_BASE_HREF, CommonModule, NgClass} from '@angular/common'; import {ImageComponent} from "../../../shared/image/image.component"; import {MangaFormatIconPipe} from "../../../_pipes/manga-format-icon.pipe"; import {MangaFormatPipe} from "../../../_pipes/manga-format.pipe"; @@ -13,11 +13,12 @@ import {NgbProgressbar} from "@ng-bootstrap/ng-bootstrap"; import {TranslocoDirective} from "@jsverse/transloco"; import {DashboardStream} from "../../../_models/dashboard/dashboard-stream"; import {StreamNamePipe} from "../../../_pipes/stream-name.pipe"; +import {RouterLink} from "@angular/router"; @Component({ selector: 'app-dashboard-stream-list-item', standalone: true, - imports: [ImageComponent, MangaFormatIconPipe, MangaFormatPipe, NgbProgressbar, TranslocoDirective, StreamNamePipe, NgClass], + imports: [ImageComponent, MangaFormatIconPipe, MangaFormatPipe, NgbProgressbar, TranslocoDirective, StreamNamePipe, NgClass, RouterLink], templateUrl: './dashboard-stream-list-item.component.html', styleUrls: ['./dashboard-stream-list-item.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush @@ -26,4 +27,5 @@ export class DashboardStreamListItemComponent { @Input({required: true}) item!: DashboardStream; @Input({required: true}) position: number = 0; @Output() hide: EventEmitter = new EventEmitter(); + protected readonly baseUrl = inject(APP_BASE_HREF); } diff --git a/UI/Web/src/app/sidenav/_components/manage-smart-filters/manage-smart-filters.component.html b/UI/Web/src/app/sidenav/_components/manage-smart-filters/manage-smart-filters.component.html index 05ea56b13..5b02fea2f 100644 --- a/UI/Web/src/app/sidenav/_components/manage-smart-filters/manage-smart-filters.component.html +++ b/UI/Web/src/app/sidenav/_components/manage-smart-filters/manage-smart-filters.component.html @@ -19,7 +19,7 @@ {{t('errored')}} } - {{f.name}} + {{f.name}}