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

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