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:
parent
77de5ccce3
commit
894b49bb76
43 changed files with 307 additions and 241 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)})}}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 || '';
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue