v0.8.3.2 - A Small Hotfix (#3194)

Co-authored-by: Robbie Davis <robbie@therobbiedavis.com>
Co-authored-by: Weblate (bot) <hosted@weblate.org>
Co-authored-by: Havokdan <havokdan@yahoo.com.br>
Co-authored-by: daydreamrabbit <devrabbit90@gmail.com>
Co-authored-by: 無情天 <kofzhanganguo@126.com>
This commit is contained in:
Joe Milazzo 2024-09-20 16:18:42 -05:00 committed by GitHub
parent 77de5ccce3
commit 894b49bb76
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
43 changed files with 307 additions and 241 deletions

View File

@ -549,12 +549,15 @@ public class OpdsController : BaseApiController
Id = readingListDto.Id.ToString(),
Title = readingListDto.Title,
Summary = readingListDto.Summary,
Links = new List<FeedLink>()
{
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 @@ public class OpdsController : BaseApiController
[HttpGet("{apiKey}/reading-list/{readingListId}")]
[Produces("application/xml")]
public async Task<IActionResult> GetReadingListItems(int readingListId, string apiKey)
public async Task<IActionResult> 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 class OpdsController : BaseApiController
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);

View File

@ -345,7 +345,7 @@ public class SettingsController : BaseApiController
if (updateBookmarks)
{
BackgroundJob.Enqueue(() => UpdateBookmarkDirectory(originalBookmarkDirectory, bookmarkDirectory));
UpdateBookmarkDirectory(originalBookmarkDirectory, bookmarkDirectory);
}
if (updateSettingsDto.EnableFolderWatching)
@ -371,6 +371,7 @@ public class SettingsController : BaseApiController
return Ok(updateSettingsDto);
}
private void UpdateBookmarkDirectory(string originalBookmarkDirectory, string bookmarkDirectory)
{
_directoryService.ExistOrCreate(bookmarkDirectory);

View File

@ -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!;

View File

@ -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!;
}

View File

@ -13,7 +13,7 @@ public class ResetPasswordDto
/// The new password
/// </summary>
[Required]
[StringLength(32, MinimumLength = 6)]
[StringLength(256, MinimumLength = 6)]
public string Password { get; init; } = default!;
/// <summary>
/// The old, existing password. If an admin is performing the change, this is not required. Otherwise, it is.

View File

@ -11,6 +11,6 @@ public class RegisterDto
/// </summary>
public string Email { get; init; } = default!;
[Required]
[StringLength(32, MinimumLength = 6)]
[StringLength(256, MinimumLength = 6)]
public string Password { get; set; } = default!;
}

View File

@ -119,6 +119,7 @@ public class CacheService : ICacheService
}
_logger.LogDebug("File Dimensions call for {Length} images took {Time}ms", dimensions.Count, sw.ElapsedMilliseconds);
return dimensions;
}

View File

@ -3,7 +3,7 @@
<TargetFramework>net8.0</TargetFramework>
<Company>kavitareader.com</Company>
<Product>Kavita</Product>
<AssemblyVersion>0.8.3.1</AssemblyVersion>
<AssemblyVersion>0.8.3.2</AssemblyVersion>
<NeutralLanguage>en</NeutralLanguage>
<TieredPGO>true</TieredPGO>
</PropertyGroup>
@ -20,4 +20,4 @@
</PackageReference>
<PackageReference Include="xunit.assert" Version="2.9.0" />
</ItemGroup>
</Project>
</Project>

View File

@ -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;

View File

@ -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;

View File

@ -1,21 +1,25 @@
<ng-container *transloco="let t; read: 'details-tab'">
<div class="details pb-3">
<div class="mb-3">
<div class="mb-3 ms-1">
<h4 class="header">{{t('genres-title')}}</h4>
<app-badge-expander [includeComma]="true" [items]="genres" [itemsTillExpander]="3">
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="openGeneric(FilterField.Genres, item.id)">{{item.title}}</a>
</ng-template>
</app-badge-expander>
<div class="ms-3">
<app-badge-expander [includeComma]="true" [items]="genres" [itemsTillExpander]="3">
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="openGeneric(FilterField.Genres, item.id)">{{item.title}}</a>
</ng-template>
</app-badge-expander>
</div>
</div>
<div class="mb-3">
<div class="mb-3 ms-1">
<h4 class="header">{{t('tags-title')}}</h4>
<app-badge-expander [includeComma]="true" [items]="tags" [itemsTillExpander]="3">
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="openGeneric(FilterField.Tags, item.id)">{{item.title}}</a>
</ng-template>
</app-badge-expander>
<div class="ms-3">
<app-badge-expander [includeComma]="true" [items]="tags" [itemsTillExpander]="3">
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="openGeneric(FilterField.Tags, item.id)">{{item.title}}</a>
</ng-template>
</app-badge-expander>
</div>
</div>
<div class="mb-3">
@ -29,7 +33,7 @@
</app-carousel-reel>
</div>
@if (genres.length > 0 || tags.length > 0) {
@if (genres.length > 0 || tags.length > 0 || webLinks.length > 0) {
<div class="setting-section-break" aria-hidden="true"></div>
}

View File

@ -2,11 +2,7 @@
<p>{{t('description')}}</p>
<form [formGroup]="settingsForm">
<p class="alert alert-warning">{{t('setting-description')}}</p>
@if (settingsForm.dirty) {
<div class="alert alert-warning">{{t('test-warning')}}</div>
}
<p class="alert alert-warning">{{t('setting-description')}} {{t('test-warning')}}</p>
<div class="row g-0 mt-2">
@if (settingsForm.get('hostName'); as formControl) {

View File

@ -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 {

View File

@ -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(

View File

@ -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]));
}
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);
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.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});

View File

@ -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;

View File

@ -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);

View File

@ -80,7 +80,7 @@
@if (chapter.isSpecial) {
{{chapter.title || chapter.range}}
} @else {
<app-entity-title [entity]="chapter" [prioritizeTitleName]="false"></app-entity-title>
<app-entity-title [entity]="chapter" [prioritizeTitleName]="false" [libraryType]="libraryType"></app-entity-title>
}
</a>
</span>

View File

@ -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)})}}
}
}

View File

@ -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 || '';

View File

@ -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) {

View File

@ -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: Series) {

View File

@ -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;

View File

@ -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<string>(this.apiUrl + 'filter/encode', filter, TextResonse);

View File

@ -22,7 +22,7 @@
</div>
@if (!item.isProvided) {
<div class="ps-1">
<a [href]="'/all-series?' + this.item.smartFilterEncoded" target="_blank">{{t('load-filter')}}</a>
<a [href]="baseUrl + 'all-series?' + this.item.smartFilterEncoded" target="_blank">{{t('load-filter')}}</a>
</div>
}
</div>

View File

@ -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<DashboardStream> = new EventEmitter<DashboardStream>();
protected readonly baseUrl = inject(APP_BASE_HREF);
}

View File

@ -19,7 +19,7 @@
<i class="fa-solid fa-triangle-exclamation red me-2" [ngbTooltip]="t('errored')"></i>
<span class="visually-hidden">{{t('errored')}}</span>
}
<a [href]="'/all-series?' + f.filter" target="_blank">{{f.name}}</a>
<a [href]="baseUrl + 'all-series?' + f.filter" target="_blank">{{f.name}}</a>
</span>
<button class="btn btn-danger float-end" (click)="deleteFilter(f)">
<i class="fa-solid fa-trash" aria-hidden="true"></i>

View File

@ -6,11 +6,13 @@ import {FormControl, FormGroup, ReactiveFormsModule} from "@angular/forms";
import {FilterPipe} from "../../../_pipes/filter.pipe";
import {ActionService} from "../../../_services/action.service";
import {NgbTooltip} from "@ng-bootstrap/ng-bootstrap";
import {RouterLink} from "@angular/router";
import {APP_BASE_HREF} from "@angular/common";
@Component({
selector: 'app-manage-smart-filters',
standalone: true,
imports: [ReactiveFormsModule, TranslocoDirective, FilterPipe, NgbTooltip],
imports: [ReactiveFormsModule, TranslocoDirective, FilterPipe, NgbTooltip, RouterLink],
templateUrl: './manage-smart-filters.component.html',
styleUrls: ['./manage-smart-filters.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
@ -20,6 +22,7 @@ export class ManageSmartFiltersComponent {
private readonly filterService = inject(FilterService);
private readonly cdRef = inject(ChangeDetectorRef);
private readonly actionService = inject(ActionService);
protected readonly baseUrl = inject(APP_BASE_HREF);
filters: Array<SmartFilter> = [];
listForm: FormGroup = new FormGroup({

View File

@ -2,12 +2,12 @@
<div class="row pt-2 g-0 list-item">
<div class="g-0">
<h5 class="mb-1 pb-0" id="item.id--{{position}}">
<span *ngIf="item.isProvided; else nonProvidedTitle">
@if (item.isProvided) {
{{item.name | streamName }}
</span>
<ng-template #nonProvidedTitle>
} @else {
{{item.name}}
</ng-template>
}
<span class="float-end">
<button class="btn btn-icon p-0" (click)="hide.emit(item)">
<i class="me-1" [ngClass]="{'fas fa-eye': item.visible, 'fa-solid fa-eye-slash': !item.visible}" aria-hidden="true"></i>
@ -29,10 +29,10 @@
<div class="ps-1" *ngIf="!item.isProvided">
<ng-container [ngSwitch]="item.streamType">
<ng-container *ngSwitchCase="SideNavStreamType.Library">
<a [href]="'/library/' + this.item.libraryId" target="_blank">{{item.library?.name}}</a>
<a [href]="baseUrl + 'library/' + this.item.libraryId" target="_blank">{{item.library?.name}}</a>
</ng-container>
<ng-container *ngSwitchCase="SideNavStreamType.SmartFilter">
<a [href]="'/all-series?' + this.item.smartFilterEncoded" target="_blank">{{t('load-filter')}}</a>
<a [href]="baseUrl + 'all-series?' + this.item.smartFilterEncoded">{{t('load-filter')}}</a>
</ng-container>
<ng-container *ngSwitchCase="SideNavStreamType.ExternalSource">
<a [href]="item.externalSource!.host! + 'login?apiKey=' + item.externalSource!.apiKey" target="_blank" rel="noopener noreferrer">{{item.externalSource!.host!}}</a>

View File

@ -1,14 +1,15 @@
import {ChangeDetectionStrategy, Component, EventEmitter, Input, Output} from '@angular/core';
import {CommonModule} from '@angular/common';
import {ChangeDetectionStrategy, Component, EventEmitter, inject, Input, Output} from '@angular/core';
import {APP_BASE_HREF, CommonModule} from '@angular/common';
import {SideNavStream} from "../../../_models/sidenav/sidenav-stream";
import {StreamNamePipe} from "../../../_pipes/stream-name.pipe";
import {TranslocoDirective} from "@jsverse/transloco";
import {SideNavStreamType} from "../../../_models/sidenav/sidenav-stream-type.enum";
import {RouterLink} from "@angular/router";
@Component({
selector: 'app-sidenav-stream-list-item',
standalone: true,
imports: [CommonModule, StreamNamePipe, TranslocoDirective],
imports: [CommonModule, StreamNamePipe, TranslocoDirective, RouterLink],
templateUrl: './sidenav-stream-list-item.component.html',
styleUrls: ['./sidenav-stream-list-item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
@ -18,4 +19,10 @@ export class SidenavStreamListItemComponent {
@Input({required: true}) position: number = 0;
@Output() hide: EventEmitter<SideNavStream> = new EventEmitter<SideNavStream>();
protected readonly SideNavStreamType = SideNavStreamType;
protected readonly baseUrl = inject(APP_BASE_HREF);
constructor() {
console.log('baseUrl', this.baseUrl);
}
}

View File

@ -8,7 +8,7 @@ import {
OnInit,
ViewChild
} from '@angular/core';
import {AsyncPipe, DecimalPipe, DOCUMENT, NgStyle, NgClass, DatePipe} from "@angular/common";
import {AsyncPipe, DecimalPipe, DOCUMENT, NgStyle, NgClass, DatePipe, Location} from "@angular/common";
import {ActivatedRoute, Router, RouterLink} from "@angular/router";
import {ImageService} from "../_services/image.service";
import {SeriesService} from "../_services/series.service";
@ -198,6 +198,7 @@ export class VolumeDetailComponent implements OnInit {
protected readonly utilityService = inject(UtilityService);
private readonly readingListService = inject(ReadingListService);
private readonly messageHub = inject(MessageHubService);
private readonly location = inject(Location);
protected readonly AgeRating = AgeRating;
@ -555,8 +556,9 @@ export class VolumeDetailComponent 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) {

View File

@ -625,7 +625,7 @@
"continue-incognito": "Unsichtbar fortsetzen",
"incognito": "Unsichtbar",
"remove-from-want-to-read": "{{actionable.remove-from-want-to-read}}",
"edit-series-alt": "Bearbeite die Serieninformationen",
"edit-series-alt": "Informationen bearbeiten",
"user-reviews-local": "Lokale Bewertungen",
"user-reviews-plus": "Externe Bewertungen",
"cover-artists-title": "{{metadata-fields.cover-artists-title}}",
@ -832,8 +832,9 @@
},
"entity-title": {
"special": "Spezial",
"issue-num": "Ausgabe #",
"chapter": "Kapitel"
"issue-num": "{{common.issue-hash-num}}",
"chapter": "{{common.chapter-num}}",
"book-num": "{{common.book-num-shorthand}}"
},
"external-series-card": {
"open-external": "Extern öffnen"
@ -867,7 +868,7 @@
"email-settings-title": "E-Mail-Einstellungen",
"outlook-label": "Outlook",
"setting-description": "Sie müssen sowohl den Hostnamen als auch die SMTP-Einstellungen ausfüllen, um die E-Mail-basierten Funktionen von Kavita nutzen zu können.",
"test-warning": "Sie müssen speichern, bevor Sie die Schaltfläche Test verwenden.",
"test-warning": "Sie müssen über gültige Einstellungen verfügen, bevor Sie auf „Testen“ klicken.",
"size-limit-tooltip": "Wie viele Bytes kann der E-Mail-Server für Anhänge verarbeiten",
"customized-templates-label": "Angepasste Templates"
},
@ -1135,7 +1136,7 @@
},
"filter-field-pipe": {
"want-to-read": "Möchte ich lesen",
"cover-artist": "Cover Künstler",
"cover-artist": "{{person-role-pipe.cover-artist}}",
"read-time": "Lesezeit",
"location": "Standort",
"file-path": "Datei-Pfad",
@ -1251,7 +1252,7 @@
"character": "Charakter",
"editor": "Editor",
"publisher": "Herausgeber",
"cover-artist": "Cover Künstler",
"cover-artist": "{{artist}}",
"translator": "Übersetzer",
"colorist": "Kolorist",
"imprint": "Impressum",

View File

@ -527,7 +527,7 @@
"artist": "Artist",
"character": "Character",
"colorist": "Colorist",
"cover-artist": "Cover Artist",
"cover-artist": "{{artist}}",
"editor": "Editor",
"inker": "Inker",
"letterer": "Letterer",
@ -788,7 +788,7 @@
"incognito": "Incognito",
"remove-from-want-to-read": "{{actionable.remove-from-want-to-read}}",
"add-to-want-to-read": "{{actionable.add-to-want-to-read}}",
"edit-series-alt": "Edit Series Information",
"edit-series-alt": "Edit Information",
"reviews-tab": "{{tabs.reviews-tab}}",
"storyline-tab": "{{tabs.storyline-tab}}",
"books-tab": "{{tabs.books-tab}}",
@ -1109,8 +1109,9 @@
"entity-title": {
"special": "Special",
"issue-num": "Issue #",
"chapter": "Chapter"
"issue-num": "{{common.issue-hash-num}}",
"chapter": "{{common.chapter-num}}",
"book-num": "{{common.book-num-shorthand}}"
},
"external-series-card": {
@ -1132,7 +1133,7 @@
"manage-email-settings": {
"description": "In order to use some functions of Kavita like Send To Device an email provider must be setup. Other features like Forgot Password require admin intervention without Email setup.",
"setting-description": "You must fill out both Host Name and SMTP settings to use email-based functionality within Kavita.",
"test-warning": "You must save before using Test button.",
"test-warning": "You must have valid settings before hitting Test.",
"send-to-warning": "If you want Send to Device to work you must setup your email settings",
"email-url-label": "Email Service URL",
"email-url-tooltip": "Use fully qualified URL of the email service. Do not include ending slash.",
@ -2188,7 +2189,7 @@
"characters": "{{metadata-fields.characters-title}}",
"collection-tags": "Collection Tags",
"colorist": "Colorist",
"cover-artist": "Cover Artist",
"cover-artist": "{{person-role-pipe.cover-artist}}",
"editor": "Editor",
"formats": "Formats",
"genres": "{{metadata-fields.genres-title}}",

View File

@ -614,7 +614,7 @@
"remove-from-want-to-read": "{{actionable.remove-from-want-to-read}}",
"incognito": "Incógnito",
"add-to-want-to-read": "{{actionable.add-to-want-to-read}}",
"edit-series-alt": "Editar informacion de la serie",
"edit-series-alt": "Editar información",
"volumes-tab": "{{tabs.volumes-tab}}",
"specials-tab": "{{tabs.specials-tab}}",
"related-tab": "{{tabs.related-tab}}",
@ -895,7 +895,7 @@
"artist": "Artista",
"writer": "Escritor",
"character": "Personaje",
"cover-artist": "Artista de portada",
"cover-artist": "{{artist}}",
"editor": "Editor",
"other": "Otro",
"publisher": "Editorial",
@ -1469,8 +1469,9 @@
},
"entity-title": {
"special": "Especial",
"chapter": "Capítulo",
"issue-num": "Número #"
"issue-num": "{{common.issue-hash-num}}",
"chapter": "{{common.chapter-num}}",
"book-num": "{{common.book-num-shorthand}}"
},
"external-series-card": {
"open-external": "Abrir externamente"
@ -1506,7 +1507,7 @@
"outlook-label": "Outlook",
"gmail-label": "Gmail",
"setting-description": "Para poder utilizar las funciones de correo electrónico de Kavita, debes rellenar los campos nombre del host y SMTP.",
"test-warning": "Debes guardar antes de utilizar el botón Probar."
"test-warning": "Debe tener configuraciones válidas antes de presionar Probar."
},
"cover-image-size": {
"xlarge": "Extragrande (1265x1795)",
@ -1658,7 +1659,7 @@
"genres": "{{metadata-fields.genres-title}}",
"release-year": "{{sort-field-pipe.release-year}}",
"writers": "{{metadata-fields.writers-title}}",
"cover-artist": "Artista de la portada",
"cover-artist": "{{person-role-pipe.cover-artist}}",
"read-progress": "Progreso de la lectura",
"editor": "Editor",
"read-time": "Tiempo de lectura",

View File

@ -4,7 +4,7 @@
"username": "{{common.username}}",
"password": "{{common.password}}",
"password-validation": "{{validation.password-validation}}",
"forgot-password": "Mot de passe oublié?",
"forgot-password": "Mot de passe oublié?",
"submit": "Se connecter"
},
"dashboard": {
@ -416,7 +416,7 @@
"other": "Autre",
"penciller": "Crayonneur",
"letterer": "Lettreur",
"cover-artist": "Artiste de la couverture",
"cover-artist": "{{artist}}",
"character": "Personnage",
"artist": "Artiste",
"inker": "Encreur",
@ -668,7 +668,7 @@
"collection-tags": "Étiquettes de la collection",
"characters": "{{metadata-fields.characters-title}}",
"languages": "Langues",
"cover-artist": "Artiste de couverture",
"cover-artist": "{{person-role-pipe.cover-artist}}",
"writers": "{{metadata-fields.writers-title}}",
"release-year": "{{sort-field-pipe.release-year}}",
"read-progress": "Progrès de lecture",
@ -987,7 +987,7 @@
"read-incognito": "Lire en mode privé",
"specials-tab": "{{tabs.specials-tab}}",
"recommendations-tab": "{{tabs.recommendations-tab}}",
"edit-series-alt": "Modifier les informations sur la série",
"edit-series-alt": "Modifier les informations",
"layout-mode-option-list": "Liste",
"add-to-want-to-read": "{{actionable.add-to-want-to-read}}",
"no-pages": "{{toasts.no-pages}}",
@ -1317,7 +1317,7 @@
"gmail-label": "Gmail",
"outlook-label": "Outlook",
"setting-description": "Vous devez remplir les paramètres Nom d'hôte et SMTP pour utiliser les fonctionnalités de messagerie électronique dans Kavita.",
"test-warning": "Vous devez enregistrer avant d'utiliser le bouton Test."
"test-warning": "Vous devez avoir des paramètres valides avant de cliquer sur Test."
},
"bulk-add-to-collection": {
"collection-label": "Collection",
@ -1332,8 +1332,9 @@
},
"entity-title": {
"special": "Spécial",
"chapter": "Chapitre",
"issue-num": "Numéro #"
"chapter": "{{common.chapter-num}}",
"issue-num": "{{common.issue-hash-num}}",
"book-num": "{{common.book-num-shorthand}}"
},
"manage-library": {
"add-library": "Ajouter une bibliothèque",

View File

@ -345,7 +345,7 @@
"read-options-alt": "Léigh roghanna",
"remove-from-want-to-read": "{{actionable.remove-from-want-to-read}}",
"add-to-want-to-read": "{{actionable.add-to-want-to-read}}",
"edit-series-alt": "Cuir Eolas faoin tSraith in Eagar",
"edit-series-alt": "Cuir Eolas in Eagar",
"storyline-tab": "{{tabs.storyline-tab}}",
"books-tab": "{{tabs.books-tab}}",
"volumes-tab": "{{tabs.volumes-tab}}",
@ -524,7 +524,7 @@
"gmail-label": "GmailName",
"description": "Chun úsáid a bhaint as roinnt feidhmeanna de chuid Kavita cosúil le Send To Device ní mór soláthraí ríomhphoist a shocrú. Teastaíonn idirghabháil riaracháin ó ghnéithe eile cosúil le Forgot Password gan socrú Ríomhphost.",
"setting-description": "Ní mór duit socruithe Ainm Óstach agus SMTP araon a líonadh chun feidhmiúlacht ríomhphost-bhunaithe a úsáid laistigh de Kavita.",
"test-warning": "Ní mór duit sábháil sula n-úsáideann tú cnaipe Tástála.",
"test-warning": "Ní mór socruithe bailí a bheith agat sula mbuaileann tú Tástáil.",
"send-to-warning": "Más mian leat Seol chuig gléas a bheith ag obair ní mór duit do shocruithe ríomhphoist a shocrú",
"email-url-label": "URL na Seirbhíse Ríomhphoist",
"email-url-tooltip": "Úsáid URL láncháilithe na seirbhíse ríomhphoist. Ná cuir deireadh le slais.",
@ -1134,7 +1134,7 @@
"characters": "{{metadata-fields.characters-title}}",
"collection-tags": "Clibeanna Bailiúcháin",
"colorist": "Dathóir",
"cover-artist": "Ealaíontóir Clúdaigh",
"cover-artist": "{{person-role-pipe.cover-artist}}",
"editor": "Eagarthóir",
"formats": "Formáidí",
"genres": "{{metadata-fields.genres-title}}",
@ -1849,8 +1849,9 @@
},
"entity-title": {
"special": "Speisialta",
"issue-num": "Eisiúint #",
"chapter": "Caibidil 1009"
"issue-num": "{{common.issue-hash-num}}",
"chapter": "{{common.chapter-num}}",
"book-num": "{{common.book-num-shorthand}}"
},
"external-series-card": {
"open-external": "Oscail Seachtrach"

View File

@ -693,11 +693,11 @@
"annual": "연간"
},
"publication-status-pipe": {
"ended": "종료",
"ongoing": "진행중",
"hiatus": "중단",
"ended": "누락",
"ongoing": "연재중",
"hiatus": "휴재",
"completed": "완결",
"cancelled": "취소"
"cancelled": "연재중단"
},
"person-role-pipe": {
"character": "캐릭터",
@ -705,7 +705,7 @@
"writer": "작가",
"other": "그 외",
"artist": "작가",
"cover-artist": "표지 작가",
"cover-artist": "{{artist}}",
"editor": "편집자",
"inker": "잉커",
"letterer": "레터러",
@ -825,7 +825,7 @@
"series-detail": {
"remove-from-want-to-read": "{{actionable.remove-from-want-to-read}}",
"add-to-want-to-read": "{{actionable.add-to-want-to-read}}",
"edit-series-alt": "시리즈 정보 편집",
"edit-series-alt": "정보 편집",
"no-pages": "{{toasts.no-pages}}",
"no-chapters": "이 볼륨에는 챕터가 없습니다. 읽을 수 없습니다.",
"cover-change": "브라우저에서 이미지를 새로 고치는 데 최대 1분이 걸릴 수 있습니다. 그때까지는 일부 페이지에 이전 이미지가 표시될 수 있습니다.",
@ -982,8 +982,9 @@
},
"entity-title": {
"special": "스페셜",
"issue-num": "이슈 #",
"chapter": "챕터"
"issue-num": "{{common.issue-hash-num}}",
"chapter": "{{common.chapter-num}}",
"book-num": "{{common.book-num-shorthand}}"
},
"manage-email-settings": {
"reset": "{{common.reset}}",
@ -1016,7 +1017,7 @@
"username-tooltip": "호스트에 대해 인증하는 데 사용되는 사용자 이름",
"host-tooltip": "이메일 서버의 발신/SMTP 주소",
"setting-description": "Kavita 내에서 이메일 기반 기능을 사용하려면 호스트 이름과 SMTP 설정을 모두 입력해야 합니다.",
"test-warning": "테스트 버튼을 사용하기 전에 반드시 저장해야 합니다."
"test-warning": "테스트를 누르기 전에 설정이 유효해야 합니다."
},
"manage-scrobble-errors": {
"edit-item-alt": "편집 {{seriesName}}",
@ -1885,7 +1886,7 @@
"filter-field-pipe": {
"collection-tags": "컬렉션 태그",
"colorist": "컬러리스트",
"cover-artist": "커버 아티스트",
"cover-artist": "{{person-role-pipe.cover-artist}}",
"editor": "편집자",
"summary": "요약",
"tags": "{{metadata-fields.tags-title}}",

View File

@ -622,7 +622,7 @@
},
"series-detail": {
"continue": "Kontynuuj",
"edit-series-alt": "Edytuj informacje o serii",
"edit-series-alt": "Edytuj informacje",
"storyline-tab": "{{tabs.storyline-tab}}",
"recommendations-tab": "{{tabs.recommendations-tab}}",
"send-to": "Plik wysłany e-mailem do {{deviceName}}",
@ -1199,7 +1199,7 @@
"size-limit-tooltip": "Ile bajtów może obsłużyć serwer poczty e-mail dla załączników",
"gmail-label": "Gmail",
"outlook-label": "Outlook",
"test-warning": "Musisz zapisać przed użyciem przycisku Test.",
"test-warning": "Przed naciśnięciem przycisku Test wprowadź prawidłowe ustawienia.",
"setting-description": "Aby korzystać z funkcji poczty e-mail w Kavita, należy wypełnić zarówno ustawienia nazwy hosta, jak i SMTP."
},
"manage-media-settings": {
@ -1403,8 +1403,9 @@
},
"entity-title": {
"special": "Special",
"chapter": "Rozdział",
"issue-num": "Wydanie #"
"chapter": "{{common.chapter-num}}",
"issue-num": "{{common.issue-hash-num}}",
"book-num": "{{common.book-num-shorthand}}"
},
"card-detail-layout": {
"jumpkey-count": "{{count}} Serii",

View File

@ -462,7 +462,7 @@
"artist": "Artista",
"character": "Personagem",
"colorist": "Colorista",
"cover-artist": "Artista de Capa",
"cover-artist": "{{artist}}",
"editor": "Editor",
"inker": "Arte-Finalista",
"letterer": "Letrista",
@ -682,7 +682,7 @@
"incognito": "Incógnito",
"remove-from-want-to-read": "{{actionable.remove-from-want-to-read}}",
"add-to-want-to-read": "{{actionable.add-to-want-to-read}}",
"edit-series-alt": "Editar Informação da Série",
"edit-series-alt": "Editar informações",
"storyline-tab": "{{tabs.storyline-tab}}",
"books-tab": "{{tabs.books-tab}}",
"volumes-tab": "{{tabs.volumes-tab}}",
@ -917,8 +917,9 @@
},
"entity-title": {
"special": "Especial",
"issue-num": "Número #",
"chapter": "Capítulo"
"issue-num": "{{common.issue-hash-num}}",
"chapter": "{{common.chapter-num}}",
"book-num": "{{common.book-num-shorthand}}"
},
"external-series-card": {
"open-external": "Abrir Externo"
@ -953,7 +954,7 @@
"username-tooltip": "O nome de utilizador usado para autenticar no host",
"outlook-label": "Outlook",
"gmail-label": "Gmail",
"test-warning": "Tem de gravar antes de usar o botão Teste.",
"test-warning": "Deve ter definições válidas antes de clicar em Teste.",
"setting-description": "Tem de preencher o nome do Host e as definições de SMTP para poder usar as funcionalidades do Kavita baseadas em email."
},
"manage-library": {
@ -1890,7 +1891,7 @@
},
"filter-field-pipe": {
"age-rating": "{{metadata-fields.age-rating-title}}",
"cover-artist": "Artista de Capa",
"cover-artist": "{{person-role-pipe.cover-artist}}",
"formats": "Formatos",
"genres": "{{metadata-fields.genres-title}}",
"libraries": "Bibliotecas",

View File

@ -462,7 +462,7 @@
"artist": "Artista",
"character": "Personagem",
"colorist": "Colorista",
"cover-artist": "Artista da Capa",
"cover-artist": "{{artist}}",
"editor": "Editor",
"inker": "Arte-finalista",
"letterer": "Letrista",
@ -682,7 +682,7 @@
"incognito": "Incógnito",
"remove-from-want-to-read": "{{actionable.remove-from-want-to-read}}",
"add-to-want-to-read": "{{actionable.add-to-want-to-read}}",
"edit-series-alt": "Editar Informações da Série",
"edit-series-alt": "Editar informações",
"storyline-tab": "{{tabs.storyline-tab}}",
"books-tab": "{{tabs.books-tab}}",
"volumes-tab": "{{tabs.volumes-tab}}",
@ -917,8 +917,9 @@
},
"entity-title": {
"special": "Especial",
"issue-num": "Número #",
"chapter": "Capítulo"
"issue-num": "{{common.issue-hash-num}}",
"chapter": "{{common.chapter-num}}",
"book-num": "{{common.book-num-shorthand}}"
},
"external-series-card": {
"open-external": "Abrir Externamente"
@ -953,7 +954,7 @@
"username-tooltip": "O nome de usuário usado para autenticação no host",
"gmail-label": "Gmail",
"outlook-label": "Outlook",
"test-warning": "Você deve salvar antes de usar o botão Teste.",
"test-warning": "Você deve ter configurações válidas antes de clicar em Testar.",
"setting-description": "Você deve preencher as configurações de nome do host e SMTP para usar a funcionalidade baseada em e-mail no Kavita."
},
"manage-library": {
@ -1171,8 +1172,8 @@
"series-tab": "{{tabs.series-tab}}",
"name-label": "Nome",
"name-validation": "Nome deve ser único",
"promote-label": "Promoção",
"promote-tooltip": "Promoção significa que a tag pode ser vista em todo o servidor, não apenas para usuários administrativos. Todas as séries que possuem essa tag ainda terão restrições de acesso do usuário.",
"promote-label": "Divulgar",
"promote-tooltip": "Divulgação significa que a etiqueta pode ser vista em todo o servidor, não apenas para usuários administrativos. Todas as séries que possuem essa etiqueta ainda terão restrições de acesso do usuário.",
"summary-label": "Sumário",
"deselect-all": "{{common.deselect-all}}",
"select-all": "{{common.select-all}}",
@ -1319,8 +1320,8 @@
"month-label": "Mês",
"ending-title": "Terminando",
"starting-title": "Começando",
"promote-label": "Promover",
"promote-tooltip": "Promoção significa que a coleção pode ser vista em todo o servidor, não apenas para você. Todas as séries desta coleção ainda terão restrições de acesso do usuário."
"promote-label": "Divulgar",
"promote-tooltip": "Divulgação significa que a coleção pode ser vista em todo o servidor, não apenas para você. Todas as séries desta coleção ainda terão restrições de acesso do usuário."
},
"import-cbl-modal": {
"close": "{{common.close}}",
@ -1705,8 +1706,8 @@
"confirm-download-size-ios": "O iOS tem problemas para baixar arquivos maiores que 200 MB; esse download pode não ser concluído.",
"confirm-delete-collections": "Tem certeza de que deseja excluir várias coleções?",
"collection-not-owned": "Você não possui esta coleção",
"collections-promoted": "Coleções promovidas",
"collections-unpromoted": "Coleções não promovidas",
"collections-promoted": "Coleções divulgadas",
"collections-unpromoted": "Coleções não divulgadas",
"collections-deleted": "Coleções excluídas",
"pdf-book-mode-screen-size": "Tela muito pequena para o modo Livro",
"stack-imported": "Pilha Importada",
@ -1714,11 +1715,11 @@
"mal-token-required": "O token MAL é obrigatório, definido nas Configurações do Usuário",
"reading-lists-deleted": "Listas de leitura excluídas",
"confirm-delete-reading-lists": "Tem certeza de que deseja excluir as listas de leitura? Isto não pode ser desfeito.",
"reading-lists-unpromoted": "Listas de leitura não promovidas",
"reading-lists-promoted": "Listas de leitura promovidas",
"reading-list-promoted": "Lista de Leitura promovida",
"reading-lists-unpromoted": "Listas de leitura não divulgadas",
"reading-lists-promoted": "Listas de leitura divulgadas",
"reading-list-promoted": "Lista de Leitura divulgadas",
"confirm-reset-server-settings": "Isso redefinirá suas configurações para os valores da primeira instalação. Tem certeza de que deseja continuar?",
"reading-list-unpromoted": "Lista de Leitura não promovida",
"reading-list-unpromoted": "Lista de Leitura não divulgadas",
"generate-colorscape-queued": "Gerar colorscape na fila para {{name}}",
"chapter-deleted": "Capítulo excluído",
"confirm-delete-chapter": "Tem certeza de que deseja excluir este capítulo? Não modificará arquivos no disco.",
@ -1754,8 +1755,8 @@
"mark-invisible": "Marcar como Invisível",
"mark-visible": "Marcar como Visível",
"import-mal-stack": "Importar Pilha do Mal",
"unpromote": "Não Promover",
"promote": "Promover",
"unpromote": "Não Divulgar",
"promote": "Divulgar",
"generate-colorscape": "Gerar ColorScape",
"new-collection": "Nova Coleção",
"multiple-selections": "Múltiplas Seleções",
@ -1765,7 +1766,7 @@
"mark-as-read-tooltip": "Marcar o progresso como totalmente lido",
"scan-library-tooltip": "Escanear a biblioteca em busca de alterações. Use o escaneamento forçado para forçar a verificação de todas as pastas",
"analyze-files-tooltip": "Analisar Arquivos quanto ao tipo e tamanho da extensão",
"unpromote-tooltip": "Restringir a visibilidade apenas à sua conta",
"unpromote-tooltip": "Restringi a visibilidade apenas à sua conta",
"generate-colorscape-tooltip": "Gerar colorscapes e quaisquer capas ausentes",
"edit-tooltip": "Editar configurações ou detalhes",
"remove-from-on-deck-tooltip": "Remover séries da exibição no Na Estante",
@ -1854,7 +1855,7 @@
"loading": "Carregando…",
"username": "Nome do usuário",
"password": "Senha",
"promoted": "Promovido",
"promoted": "Divulgado",
"select-all": "Selecionar Tudo",
"deselect-all": "Deselecionar Todos",
"series-count": "{{num}} Séries",
@ -1893,7 +1894,7 @@
"characters": "{{metadata-fields.characters-title}}",
"collection-tags": "Coleção de Tags",
"colorist": "Colorista",
"cover-artist": "Artista da Capa",
"cover-artist": "{{person-role-pipe.cover-artist}}",
"editor": "Editor",
"genres": "{{metadata-fields.genres-title}}",
"inker": "Arte-finalista",

View File

@ -158,7 +158,7 @@
"pdf-theme-label": "主题",
"pdf-scroll-mode-label": "滚动模式",
"pdf-scroll-mode-tooltip": "您如何滚动页面。垂直/水平和点击翻页(无滚动)",
"pdf-spread-mode-label": "页面传播模式",
"pdf-spread-mode-label": "页面阅读模式",
"background-color-tooltip": "图像阅读器的背景颜色",
"auto-close-menu-tooltip": "菜单应该自动关闭",
"show-screen-hints-tooltip": "显示覆盖以帮助了解分页区域和方向",
@ -206,7 +206,7 @@
},
"restriction-selector": {
"title": "年龄分级限制",
"description": "选中后,全部系列和至少存在一个条目的阅读清单,其年龄限制大于下面选择的,将从结果中移除。",
"description": "选中后,全部系列和至少存在一个条目的阅读列表,其年龄限制大于下面选择的,将从结果中移除。",
"not-applicable-for-admins": "不适用于管理员。",
"age-rating-label": "{{metadata-fields.age-rating-title}}",
"no-restriction": "无限制",
@ -405,7 +405,7 @@
"cbl-conflict-reason-pipe": {
"all-series-missing": "您的账户无法访问列表中的所有系列或者Kavita在列表中没有任何内容。",
"chapter-missing": "{{series}}: Kavita缺少第{{chapter}}章节。此项目将被跳过。",
"empty-file": "cbl文件为空,没有任何操作可以进行。",
"empty-file": "cbl 文件为空,没有任何操作可以进行。",
"name-conflict": "您的账户已经存在一个与cbl文件匹配的阅读列表{{readingListName}})。",
"series-collision": "系列{{seriesLink}}与另一个资料库中同名的系列发生冲突。",
"series-missing": "系列{{series}}在Kavita中缺失或者您的账户没有权限。所有带有此系列的项目将在导入时被跳过。",
@ -462,7 +462,7 @@
"artist": "设计师",
"character": "角色",
"colorist": "上色师",
"cover-artist": "封面设计",
"cover-artist": "{{artist}}",
"editor": "编辑",
"inker": "上墨师",
"letterer": "嵌字师",
@ -682,7 +682,7 @@
"incognito": "隐身模式",
"remove-from-want-to-read": "{{actionable.remove-from-want-to-read}}",
"add-to-want-to-read": "{{actionable.add-to-want-to-read}}",
"edit-series-alt": "编辑系列信息",
"edit-series-alt": "編輯訊息",
"storyline-tab": "{{tabs.storyline-tab}}",
"books-tab": "{{tabs.books-tab}}",
"volumes-tab": "{{tabs.volumes-tab}}",
@ -752,7 +752,7 @@
"home": "主页",
"want-to-read": "想读",
"collections": "收藏",
"reading-lists": "阅读清单",
"reading-lists": "阅读列表",
"bookmarks": "书签",
"filter-label": "{{common.filter}}",
"all-series": "所有系列",
@ -789,7 +789,7 @@
"manage-collection-label": "管理收藏",
"manage-collection-tooltip": "是否允许Kavita根据ComicInfo.xml/opf文件中的SeriesGroup标签创建收藏",
"manage-reading-list-label": "管理阅读列表",
"manage-reading-list-tooltip": "是否允许Kavita根据ComicInfo.xml/opf文件中的StoryArc/StoryArcNumber和AlternativeSeries/AlternativeCount标签创建阅读清单",
"manage-reading-list-tooltip": "是否允许Kavita根据ComicInfo.xml/opf文件中的StoryArc/StoryArcNumber和AlternativeSeries/AlternativeCount标签创建阅读列表",
"allow-scrobbling-label": "允许Scrobbling",
"allow-scrobbling-tooltip": "是否允许Kavita将阅读事件、想读状态、评分和评论记录至已配置的Scrobble服务提供商本功能仅在激活服务器的Kavita+订阅后才会生效。",
"folder-watching-label": "监控文件夹",
@ -917,8 +917,9 @@
},
"entity-title": {
"special": "特刊",
"issue-num": "期 #",
"chapter": "章节"
"issue-num": "{{common.issue-hash-num}}",
"chapter": "{{common.chapter-num}}",
"book-num": "{{common.book-num-shorthand}}"
},
"external-series-card": {
"open-external": "打开外部链接"
@ -954,7 +955,7 @@
"gmail-label": "Gmail",
"outlook-label": "Outlook",
"setting-description": "您必须填写主机名和 SMTP 设置才能使用 Kavita 中的基于电子邮件的功能。",
"test-warning": "使用测试按钮之前必须保存。"
"test-warning": "在點擊“測試”之前,您必須具有有效的設定。"
},
"manage-library": {
"title": "资料库",
@ -1219,9 +1220,9 @@
"bulk-select-label": "批量选择项目"
},
"reading-lists": {
"title": "阅读清单",
"title": "阅读列表",
"item-count": "{{common.item-count}}",
"no-data": "没有阅读清单。",
"no-data": "没有阅读列表。",
"create-one-part-1": "尝试创建",
"create-one-part-2": "一个"
},
@ -1272,7 +1273,7 @@
"tags": "标签",
"genres": "{{metadata-fields.genres-title}}",
"libraries": "资料库",
"reading-lists": "阅读清单",
"reading-lists": "阅读列表",
"collections": "收藏",
"close": "{{common.close}}",
"loading": "{{common.loading}}",
@ -1295,17 +1296,17 @@
"close": "{{common.close}}"
},
"add-to-list-modal": {
"title": "添加到阅读清单",
"title": "添加到阅读列表",
"close": "{{common.close}}",
"filter-label": "{{common.filter}}",
"promoted-alt": "{{common.promoted}}",
"no-data": "尚未创建任何清单",
"no-data": "尚未创建任何列表",
"loading": "{{common.loading}}",
"reading-list-label": "阅读清单",
"reading-list-label": "阅读列表",
"create": "{{common.create}}"
},
"edit-reading-list-modal": {
"title": "编辑阅读清单 {{name}}",
"title": "编辑阅读列表 {{name}}",
"general-tab": "{{tabs.general-tab}}",
"cover-image-tab": "{{tabs.cover-tab}}",
"close": "{{common.close}}",
@ -1645,7 +1646,7 @@
"email-service-reset": "电子邮件服务已重置",
"email-service-reachable": "Kavita电子邮件服务连接成功",
"email-service-unresponsive": "电子邮件服务的网址没有响应。",
"refresh-covers-queued": "已为{{name}}排队刷新封面",
"refresh-covers-queued": "刷新 {{name}} 队列中的封面",
"library-file-analysis-queued": "已为{{name}}排队进行资料库文件分析",
"entity-read": "{{name}}已标记为已读",
"entity-unread": "{{name}}已标记为未读",
@ -1713,13 +1714,13 @@
"confirm-delete-theme": "删除该主题将从磁盘中删除它。您可以在删除之前从临时目录中获取它",
"mal-token-required": "需要 MAL 令牌,在用户设置中设置",
"reading-lists-deleted": "阅读列表已删除",
"reading-lists-unpromoted": "未推广的阅读清单",
"reading-lists-unpromoted": "未推广的阅读列表",
"confirm-delete-reading-lists": "您确定要删除阅读列表吗?此操作无法撤消。",
"reading-lists-promoted": "推广阅读清单",
"reading-list-promoted": "提升阅读清单",
"reading-list-unpromoted": "阅读清单未推广",
"reading-lists-promoted": "推广阅读列表",
"reading-list-promoted": "提升阅读列表",
"reading-list-unpromoted": "阅读列表未推广",
"confirm-reset-server-settings": "这会将您的设置重置为首次安装值。您确定要继续吗?",
"generate-colorscape-queued": "为 {{name}} 生成排队的彩色图案",
"generate-colorscape-queued": "为 {{name}} 生成排队的色彩主题",
"volume-deleted": "卷已删除",
"confirm-delete-chapter": "您确定要删除此章节吗?它不会修改磁盘上的文件。",
"confirm-delete-volume": "您确定要删除此卷吗?它不会修改磁盘上的文件。",
@ -1739,7 +1740,7 @@
"remove-from-want-to-read": "从想读中移除",
"remove-from-on-deck": "从最近阅读中移除",
"others": "其他",
"add-to-reading-list": "添加到阅读清单",
"add-to-reading-list": "添加到阅读列表",
"add-to-collection": "添加到收藏",
"send-to": "发送到",
"delete": "删除",
@ -1757,7 +1758,7 @@
"promote": "推广",
"unpromote": "取消推广",
"new-collection": "新收藏",
"generate-colorscape": "生成彩色图案",
"generate-colorscape": "生成色彩主题",
"multiple-selections": "多项选择",
"scan-library-tooltip": "扫描库以查看是否有更改。使用强制扫描检查每个文件夹",
"edit-tooltip": "编辑设置或详细信息",
@ -1777,7 +1778,7 @@
"promote-tooltip": "使项目对所有用户可见",
"settings-tooltip": "查看设置或详细信息",
"analyze-files-tooltip": "分析文件的扩展类型和大小",
"generate-colorscape-tooltip": "生成色彩图案和任何缺失的封面",
"generate-colorscape-tooltip": "生成色彩主题和任何缺失的封面",
"refresh-covers-tooltip": "重新生成所有封面",
"mark-as-unread-tooltip": "将进度标记为未读",
"add-to-want-to-read-tooltip": "添加系列到想读",
@ -1814,7 +1815,7 @@
"pdf-vertical": "垂直滚动",
"pdf-horizontal": "水平滚动",
"pdf-page": "点击翻页",
"pdf-none": "没有",
"pdf-none": "",
"pdf-light": "明亮",
"pdf-multiple": "默认",
"pdf-dark": "黑暗",
@ -1892,7 +1893,7 @@
"characters": "{{metadata-fields.characters-title}}",
"collection-tags": "收藏标签",
"colorist": "上色师",
"cover-artist": "封面设计",
"cover-artist": "{{person-role-pipe.cover-artist}}",
"editor": "编辑",
"formats": "格式",
"genres": "{{metadata-fields.genres-title}}",
@ -2164,7 +2165,7 @@
"user-stats": "状态",
"theme": "主题",
"customize": "自定义",
"cbl-import": "CBL 阅读清单",
"cbl-import": "CBL 阅读列表",
"mal-stack-import": "MAL Stack",
"info-section-title": "信息"
},

View File

@ -29,6 +29,7 @@ import {provideTranslocoPersistTranslations} from "@jsverse/transloco-persist-tr
import {LazyLoadImageModule} from "ng-lazyload-image";
import {getSaver, SAVER} from "./app/_providers/saver.provider";
import {distinctUntilChanged} from "rxjs/operators";
import {APP_BASE_HREF, PlatformLocation} from "@angular/common";
const disableAnimations = !('animate' in document.documentElement);
@ -115,6 +116,10 @@ const translocoOptions = {
} as TranslocoConfig
};
function getBaseHref(platformLocation: PlatformLocation): string {
return platformLocation.getBaseHrefFromDOM();
}
bootstrapApplication(AppComponent, {
providers: [
importProvidersFrom(BrowserModule,
@ -149,6 +154,11 @@ bootstrapApplication(AppComponent, {
preLoad,
Title,
{ provide: SAVER, useFactory: getSaver },
{
provide: APP_BASE_HREF,
useFactory: getBaseHref,
deps: [PlatformLocation]
},
provideHttpClient(withInterceptorsFromDi())
]
} as ApplicationConfig)

View File

@ -2,7 +2,7 @@
"openapi": "3.0.1",
"info": {
"title": "Kavita",
"description": "Kavita provides a set of APIs that are authenticated by JWT. JWT token can be copied from local storage. Assume all fields of a payload are required. Built against v0.8.2.12",
"description": "Kavita provides a set of APIs that are authenticated by JWT. JWT token can be copied from local storage. Assume all fields of a payload are required. Built against v0.8.3.2",
"license": {
"name": "GPL-3.0",
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
@ -4383,6 +4383,15 @@
"schema": {
"type": "string"
}
},
{
"name": "pageNumber",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 0
}
}
],
"responses": {
@ -16084,7 +16093,7 @@
"type": "string"
},
"password": {
"maxLength": 32,
"maxLength": 256,
"minLength": 6,
"type": "string"
},
@ -16144,7 +16153,7 @@
"type": "string"
},
"password": {
"maxLength": 32,
"maxLength": 256,
"minLength": 6,
"type": "string"
}
@ -19134,7 +19143,7 @@
"nullable": true
},
"password": {
"maxLength": 32,
"maxLength": 256,
"minLength": 6,
"type": "string"
}
@ -19273,7 +19282,7 @@
"description": "The Username of the User"
},
"password": {
"maxLength": 32,
"maxLength": 256,
"minLength": 6,
"type": "string",
"description": "The new password"