mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Release Polishing (#2325)
Co-authored-by: Robbie Davis <robbie@therobbiedavis.com>
This commit is contained in:
parent
2d3a0c1eda
commit
8e85ba069c
@ -54,7 +54,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
|
||||
<PackageReference Include="CsvHelper" Version="30.0.1" />
|
||||
<PackageReference Include="Docnet.Core" Version="2.6.0" />
|
||||
<PackageReference Include="EasyCaching.InMemory" Version="1.9.1" />
|
||||
<PackageReference Include="ExCSS" Version="4.2.2" />
|
||||
|
@ -268,6 +268,7 @@ public class AccountController : BaseApiController
|
||||
dto.RefreshToken = await _tokenService.CreateRefreshToken(user);
|
||||
dto.KavitaVersion = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.InstallVersion))
|
||||
.Value;
|
||||
dto.Preferences = _mapper.Map<UserPreferencesDto>(user.UserPreferences);
|
||||
return Ok(dto);
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ your reading collection with your friends and family!
|
||||
- Ability to manage users with rich Role-based management for age restrictions, abilities within the app, etc
|
||||
- Rich web readers supporting webtoon, continuous reading mode (continue without leaving the reader), virtual pages (epub), etc
|
||||
- Full Localization Support
|
||||
- Ability to customize your dashboard and side nav with smart filters
|
||||
- Ability to customize your dashboard and side nav with smart filters, custom order and visibility toggles.
|
||||
|
||||
|
||||
## Support
|
||||
@ -110,10 +110,6 @@ Thank you to [Weblate](https://hosted.weblate.org/engage/kavita/) who hosts our
|
||||
<img src="https://hosted.weblate.org/widgets/kavita/-/horizontal-blue.svg" alt="Translation status" />
|
||||
</a>
|
||||
|
||||
## Huntr
|
||||
We would like to extend a big thank you to [Huntr](https://huntr.dev/repos/kareadita/kavita) who has worked with Kavita in reporting security vulnerabilities. If you are interested in
|
||||
being paid to help secure Kavita, please give them a try.
|
||||
|
||||
## PikaPods
|
||||
If you are looking to try your hand at self-hosting but lack the machine, [PikaPods](https://www.pikapods.com/pods?run=kavita) is a great service that
|
||||
allows you to easily spin up a server. 20% of app revenues are contributed back to Kavita via OpenCollective.
|
||||
|
@ -7,4 +7,4 @@ Security is maintained on latest stable version only.
|
||||
## Reporting a Vulnerability
|
||||
|
||||
|
||||
Please reach out to majora2007 via our Discord or you can (and should) report your vulnerability via [Huntr](https://huntr.dev/repos/kareadita/kavita).
|
||||
Please reach out to majora2007 via our Discord or you can (and should) report your vulnerability via Github Security Disclosure.
|
||||
|
@ -43,8 +43,7 @@ export class ActionService implements OnDestroy {
|
||||
|
||||
constructor(private libraryService: LibraryService, private seriesService: SeriesService,
|
||||
private readerService: ReaderService, private toastr: ToastrService, private modalService: NgbModal,
|
||||
private confirmService: ConfirmService, private memberService: MemberService, private deviceService: DeviceService,
|
||||
private translocoService: TranslocoService) { }
|
||||
private confirmService: ConfirmService, private memberService: MemberService, private deviceService: DeviceService) { }
|
||||
|
||||
ngOnDestroy() {
|
||||
this.onDestroy.next();
|
||||
@ -66,7 +65,7 @@ export class ActionService implements OnDestroy {
|
||||
const force = false; // await this.promptIfForce();
|
||||
|
||||
this.libraryService.scan(library.id, force).pipe(take(1)).subscribe((res: any) => {
|
||||
this.toastr.info(this.translocoService.translate('toasts.scan-queued', {name: library.name}));
|
||||
this.toastr.info(translate('toasts.scan-queued', {name: library.name}));
|
||||
if (callback) {
|
||||
callback(library);
|
||||
}
|
||||
@ -95,7 +94,7 @@ export class ActionService implements OnDestroy {
|
||||
const forceUpdate = true; //await this.promptIfForce();
|
||||
|
||||
this.libraryService.refreshMetadata(library?.id, forceUpdate).pipe(take(1)).subscribe((res: any) => {
|
||||
this.toastr.info(this.translocoService.translate('toasts.scan-queued', {name: library.name}));
|
||||
this.toastr.info(translate('toasts.scan-queued', {name: library.name}));
|
||||
if (callback) {
|
||||
callback(library);
|
||||
}
|
||||
@ -129,7 +128,7 @@ export class ActionService implements OnDestroy {
|
||||
}
|
||||
|
||||
this.libraryService.analyze(library?.id).pipe(take(1)).subscribe((res: any) => {
|
||||
this.toastr.info(this.translocoService.translate('toasts.library-file-analysis-queued', {name: library.name}));
|
||||
this.toastr.info(translate('toasts.library-file-analysis-queued', {name: library.name}));
|
||||
if (callback) {
|
||||
callback(library);
|
||||
}
|
||||
@ -144,7 +143,7 @@ export class ActionService implements OnDestroy {
|
||||
markSeriesAsRead(series: Series, callback?: SeriesActionCallback) {
|
||||
this.seriesService.markRead(series.id).pipe(take(1)).subscribe(res => {
|
||||
series.pagesRead = series.pages;
|
||||
this.toastr.success(this.translocoService.translate('toasts.entity-read', {name: series.name}));
|
||||
this.toastr.success(translate('toasts.entity-read', {name: series.name}));
|
||||
if (callback) {
|
||||
callback(series);
|
||||
}
|
||||
@ -159,7 +158,7 @@ export class ActionService implements OnDestroy {
|
||||
markSeriesAsUnread(series: Series, callback?: SeriesActionCallback) {
|
||||
this.seriesService.markUnread(series.id).pipe(take(1)).subscribe(res => {
|
||||
series.pagesRead = 0;
|
||||
this.toastr.success(this.translocoService.translate('toasts.entity-unread', {name: series.name}));
|
||||
this.toastr.success(translate('toasts.entity-unread', {name: series.name}));
|
||||
if (callback) {
|
||||
callback(series);
|
||||
}
|
||||
@ -173,7 +172,7 @@ export class ActionService implements OnDestroy {
|
||||
*/
|
||||
async scanSeries(series: Series, callback?: SeriesActionCallback) {
|
||||
this.seriesService.scan(series.libraryId, series.id).pipe(take(1)).subscribe((res: any) => {
|
||||
this.toastr.info(this.translocoService.translate('toasts.scan-queued', {name: series.name}));
|
||||
this.toastr.info(translate('toasts.scan-queued', {name: series.name}));
|
||||
if (callback) {
|
||||
callback(series);
|
||||
}
|
||||
@ -187,7 +186,7 @@ export class ActionService implements OnDestroy {
|
||||
*/
|
||||
analyzeFilesForSeries(series: Series, callback?: SeriesActionCallback) {
|
||||
this.seriesService.analyzeFiles(series.libraryId, series.id).pipe(take(1)).subscribe((res: any) => {
|
||||
this.toastr.info(this.translocoService.translate('toasts.scan-queued', {name: series.name}));
|
||||
this.toastr.info(translate('toasts.scan-queued', {name: series.name}));
|
||||
if (callback) {
|
||||
callback(series);
|
||||
}
|
||||
@ -208,7 +207,7 @@ export class ActionService implements OnDestroy {
|
||||
}
|
||||
|
||||
this.seriesService.refreshMetadata(series).pipe(take(1)).subscribe((res: any) => {
|
||||
this.toastr.info(this.translocoService.translate('toasts.refresh-covers-queued', {name: series.name}));
|
||||
this.toastr.info(translate('toasts.refresh-covers-queued', {name: series.name}));
|
||||
if (callback) {
|
||||
callback(series);
|
||||
}
|
||||
@ -225,7 +224,7 @@ export class ActionService implements OnDestroy {
|
||||
this.readerService.markVolumeRead(seriesId, volume.id).pipe(take(1)).subscribe(() => {
|
||||
volume.pagesRead = volume.pages;
|
||||
volume.chapters?.forEach(c => c.pagesRead = c.pages);
|
||||
this.toastr.success(this.translocoService.translate('toasts.mark-read'));
|
||||
this.toastr.success(translate('toasts.mark-read'));
|
||||
|
||||
if (callback) {
|
||||
callback(volume);
|
||||
@ -243,7 +242,7 @@ export class ActionService implements OnDestroy {
|
||||
this.readerService.markVolumeUnread(seriesId, volume.id).subscribe(() => {
|
||||
volume.pagesRead = 0;
|
||||
volume.chapters?.forEach(c => c.pagesRead = 0);
|
||||
this.toastr.success(this.translocoService.translate('toasts.mark-unread'));
|
||||
this.toastr.success(translate('toasts.mark-unread'));
|
||||
if (callback) {
|
||||
callback(volume);
|
||||
}
|
||||
@ -259,7 +258,7 @@ export class ActionService implements OnDestroy {
|
||||
markChapterAsRead(libraryId: number, seriesId: number, chapter: Chapter, callback?: ChapterActionCallback) {
|
||||
this.readerService.saveProgress(libraryId, seriesId, chapter.volumeId, chapter.id, chapter.pages).pipe(take(1)).subscribe(results => {
|
||||
chapter.pagesRead = chapter.pages;
|
||||
this.toastr.success(this.translocoService.translate('toasts.mark-read'));
|
||||
this.toastr.success(translate('toasts.mark-read'));
|
||||
if (callback) {
|
||||
callback(chapter);
|
||||
}
|
||||
@ -275,7 +274,7 @@ export class ActionService implements OnDestroy {
|
||||
markChapterAsUnread(libraryId: number, seriesId: number, chapter: Chapter, callback?: ChapterActionCallback) {
|
||||
this.readerService.saveProgress(libraryId, seriesId, chapter.volumeId, chapter.id, 0).pipe(take(1)).subscribe(results => {
|
||||
chapter.pagesRead = 0;
|
||||
this.toastr.success(this.translocoService.translate('toasts.mark-unread'));
|
||||
this.toastr.success(translate('toasts.mark-unread'));
|
||||
if (callback) {
|
||||
callback(chapter);
|
||||
}
|
||||
@ -296,7 +295,7 @@ export class ActionService implements OnDestroy {
|
||||
volume.chapters?.forEach(c => c.pagesRead = c.pages);
|
||||
});
|
||||
chapters?.forEach(c => c.pagesRead = c.pages);
|
||||
this.toastr.success(this.translocoService.translate('toasts.mark-read'));
|
||||
this.toastr.success(translate('toasts.mark-read'));
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
@ -317,7 +316,7 @@ export class ActionService implements OnDestroy {
|
||||
volume.chapters?.forEach(c => c.pagesRead = 0);
|
||||
});
|
||||
chapters?.forEach(c => c.pagesRead = 0);
|
||||
this.toastr.success(this.translocoService.translate('toasts.mark-unread'));
|
||||
this.toastr.success(translate('toasts.mark-unread'));
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
@ -335,7 +334,7 @@ export class ActionService implements OnDestroy {
|
||||
series.forEach(s => {
|
||||
s.pagesRead = s.pages;
|
||||
});
|
||||
this.toastr.success(this.translocoService.translate('toasts.mark-read'));
|
||||
this.toastr.success(translate('toasts.mark-read'));
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
@ -353,7 +352,7 @@ export class ActionService implements OnDestroy {
|
||||
series.forEach(s => {
|
||||
s.pagesRead = s.pages;
|
||||
});
|
||||
this.toastr.success(this.translocoService.translate('toasts.mark-unread'));
|
||||
this.toastr.success(translate('toasts.mark-unread'));
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
@ -396,7 +395,7 @@ export class ActionService implements OnDestroy {
|
||||
|
||||
removeMultipleSeriesFromWantToReadList(seriesIds: Array<number>, callback?: VoidActionCallback) {
|
||||
this.memberService.removeSeriesToWantToRead(seriesIds).subscribe(() => {
|
||||
this.toastr.success(this.translocoService.translate('toasts.series-removed-want-to-read'));
|
||||
this.toastr.success(translate('toasts.series-removed-want-to-read'));
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
@ -547,7 +546,7 @@ export class ActionService implements OnDestroy {
|
||||
return;
|
||||
}
|
||||
this.seriesService.deleteMultipleSeries(seriesIds.map(s => s.id)).pipe(take(1)).subscribe(() => {
|
||||
this.toastr.success(this.translocoService.translate('toasts.series-deleted'));
|
||||
this.toastr.success(translate('toasts.series-deleted'));
|
||||
|
||||
if (callback) {
|
||||
callback(true);
|
||||
@ -565,7 +564,7 @@ export class ActionService implements OnDestroy {
|
||||
|
||||
this.seriesService.delete(series.id).subscribe((res: boolean) => {
|
||||
if (callback) {
|
||||
this.toastr.success(this.translocoService.translate('toasts.series-deleted'));
|
||||
this.toastr.success(translate('toasts.series-deleted'));
|
||||
callback(res);
|
||||
}
|
||||
});
|
||||
@ -573,7 +572,7 @@ export class ActionService implements OnDestroy {
|
||||
|
||||
sendToDevice(chapterIds: Array<number>, device: Device, callback?: VoidActionCallback) {
|
||||
this.deviceService.sendTo(chapterIds, device.id).subscribe(() => {
|
||||
this.toastr.success(this.translocoService.translate('toasts.file-send-to', {name: device.name}));
|
||||
this.toastr.success(translate('toasts.file-send-to', {name: device.name}));
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
@ -582,7 +581,7 @@ export class ActionService implements OnDestroy {
|
||||
|
||||
sendSeriesToDevice(seriesId: number, device: Device, callback?: VoidActionCallback) {
|
||||
this.deviceService.sendSeriesTo(seriesId, device.id).subscribe(() => {
|
||||
this.toastr.success(this.translocoService.translate('toasts.file-send-to', {name: device.name}));
|
||||
this.toastr.success(translate('toasts.file-send-to', {name: device.name}));
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
|
@ -12,3 +12,7 @@
|
||||
.muted {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
a.read-more-link {
|
||||
white-space: nowrap;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import {Component, DestroyRef, HostListener, inject, Inject, OnInit} from '@angular/core';
|
||||
import { NavigationStart, Router, RouterOutlet } from '@angular/router';
|
||||
import {map, pluck, shareReplay, take} from 'rxjs/operators';
|
||||
import {map, shareReplay, take} from 'rxjs/operators';
|
||||
import { AccountService } from './_services/account.service';
|
||||
import { LibraryService } from './_services/library.service';
|
||||
import { NavService } from './_services/nav.service';
|
||||
@ -12,7 +12,6 @@ import {ThemeService} from "./_services/theme.service";
|
||||
import { SideNavComponent } from './sidenav/_components/side-nav/side-nav.component';
|
||||
import {NavHeaderComponent} from "./nav/_components/nav-header/nav-header.component";
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
import {translate, TranslocoService} from "@ngneat/transloco";
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@ -26,7 +25,6 @@ export class AppComponent implements OnInit {
|
||||
transitionState$!: Observable<boolean>;
|
||||
|
||||
destroyRef = inject(DestroyRef);
|
||||
translocoService = inject(TranslocoService);
|
||||
|
||||
constructor(private accountService: AccountService, public navService: NavService,
|
||||
private libraryService: LibraryService,
|
||||
|
@ -18,7 +18,7 @@ import { ReadingList } from 'src/app/_models/reading-list';
|
||||
import { CollectionTagService } from 'src/app/_services/collection-tag.service';
|
||||
import {CommonModule} from "@angular/common";
|
||||
import {FilterPipe} from "../../../pipe/filter.pipe";
|
||||
import {TranslocoDirective, TranslocoService} from "@ngneat/transloco";
|
||||
import {translate, TranslocoDirective, TranslocoService} from "@ngneat/transloco";
|
||||
|
||||
@Component({
|
||||
selector: 'app-bulk-add-to-collection',
|
||||
@ -46,7 +46,6 @@ export class BulkAddToCollectionComponent implements OnInit, AfterViewInit {
|
||||
|
||||
collectionTitleTrackby = (index: number, item: CollectionTag) => `${item.title}`;
|
||||
|
||||
translocoService = inject(TranslocoService);
|
||||
|
||||
@ViewChild('title') inputElem!: ElementRef<HTMLInputElement>;
|
||||
|
||||
@ -83,7 +82,7 @@ export class BulkAddToCollectionComponent implements OnInit, AfterViewInit {
|
||||
create() {
|
||||
const tagName = this.listForm.value.title;
|
||||
this.collectionService.addByMultiple(0, this.seriesIds, tagName).subscribe(() => {
|
||||
this.toastr.success(this.translocoService.translate('toasts.series-added-to-collection', {collectionName: tagName}));
|
||||
this.toastr.success(translate('toasts.series-added-to-collection', {collectionName: tagName}));
|
||||
this.modal.close();
|
||||
});
|
||||
}
|
||||
@ -92,7 +91,7 @@ export class BulkAddToCollectionComponent implements OnInit, AfterViewInit {
|
||||
if (this.seriesIds.length === 0) return;
|
||||
|
||||
this.collectionService.addByMultiple(tag.id, this.seriesIds, '').subscribe(() => {
|
||||
this.toastr.success(this.translocoService.translate('toasts.series-added-to-collection', {collectionName: tag.title}));
|
||||
this.toastr.success(translate('toasts.series-added-to-collection', {collectionName: tag.title}));
|
||||
this.modal.close();
|
||||
});
|
||||
|
||||
|
@ -33,7 +33,7 @@ import { UploadService } from 'src/app/_services/upload.service';
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
import {CommonModule} from "@angular/common";
|
||||
import {CoverImageChooserComponent} from "../../cover-image-chooser/cover-image-chooser.component";
|
||||
import {TranslocoDirective, TranslocoService} from "@ngneat/transloco";
|
||||
import {translate, TranslocoDirective, TranslocoService} from "@ngneat/transloco";
|
||||
|
||||
|
||||
enum TabID {
|
||||
@ -65,7 +65,6 @@ export class EditCollectionTagsComponent implements OnInit {
|
||||
imageUrls: Array<string> = [];
|
||||
selectedCover: string = '';
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
translocoService = inject(TranslocoService);
|
||||
|
||||
get hasSomeSelected() {
|
||||
return this.selections != null && this.selections.hasSomeSelected();
|
||||
@ -172,7 +171,7 @@ export class EditCollectionTagsComponent implements OnInit {
|
||||
tag.id = this.tag.id;
|
||||
|
||||
if (unselectedIds.length == this.series.length &&
|
||||
!await this.confirmService.confirm(this.translocoService.translate('toasts.no-series-collection-warning'))) {
|
||||
!await this.confirmService.confirm(translate('toasts.no-series-collection-warning'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -187,7 +186,7 @@ export class EditCollectionTagsComponent implements OnInit {
|
||||
|
||||
forkJoin(apis).subscribe(() => {
|
||||
this.modal.close({success: true, coverImageUpdated: selectedIndex > 0});
|
||||
this.toastr.success(this.translocoService.translate('toasts.collection-updated'));
|
||||
this.toastr.success(translate('toasts.collection-updated'));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,7 @@ import {BytesPipe} from "../../pipe/bytes.pipe";
|
||||
import {BadgeExpanderComponent} from "../../shared/badge-expander/badge-expander.component";
|
||||
import {TagBadgeComponent} from "../../shared/tag-badge/tag-badge.component";
|
||||
import {PersonBadgeComponent} from "../../shared/person-badge/person-badge.component";
|
||||
import {TranslocoDirective, TranslocoService} from "@ngneat/transloco";
|
||||
import {translate, TranslocoDirective, TranslocoService} from "@ngneat/transloco";
|
||||
import {CardActionablesComponent} from "../../_single-module/card-actionables/card-actionables.component";
|
||||
|
||||
enum TabID {
|
||||
@ -73,7 +73,6 @@ export class CardDetailDrawerComponent implements OnInit {
|
||||
@Input() libraryId: number = 0;
|
||||
@Input({required: true}) data!: Volume | Chapter;
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly translocoService = inject(TranslocoService);
|
||||
|
||||
|
||||
/**
|
||||
@ -209,7 +208,7 @@ export class CardDetailDrawerComponent implements OnInit {
|
||||
|
||||
resetCoverImage() {
|
||||
this.uploadService.resetChapterCoverLock(this.chapter.id).subscribe(() => {
|
||||
this.toastr.info(this.translocoService.translate('toasts.regen-cover'));
|
||||
this.toastr.info(translate('toasts.regen-cover'));
|
||||
});
|
||||
}
|
||||
|
||||
@ -262,7 +261,7 @@ export class CardDetailDrawerComponent implements OnInit {
|
||||
|
||||
readChapter(chapter: Chapter, incognito: boolean = false) {
|
||||
if (chapter.pages === 0) {
|
||||
this.toastr.error(this.translocoService.translate('toasts.no-pages'));
|
||||
this.toastr.error(translate('toasts.no-pages'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -273,7 +272,7 @@ export class CardDetailDrawerComponent implements OnInit {
|
||||
|
||||
download(chapter: Chapter) {
|
||||
if (this.downloadInProgress) {
|
||||
this.toastr.info(this.translocoService.translate('toasts.download-in-progress'));
|
||||
this.toastr.info(translate('toasts.download-in-progress'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ import {ImageComponent} from "../../shared/image/image.component";
|
||||
import {DownloadIndicatorComponent} from "../download-indicator/download-indicator.component";
|
||||
import {EntityInfoCardsComponent} from "../entity-info-cards/entity-info-cards.component";
|
||||
import {NgbProgressbar, NgbTooltip} from "@ng-bootstrap/ng-bootstrap";
|
||||
import {TranslocoDirective, TranslocoService} from "@ngneat/transloco";
|
||||
import {translate, TranslocoDirective} from "@ngneat/transloco";
|
||||
import {CardActionablesComponent} from "../../_single-module/card-actionables/card-actionables.component";
|
||||
|
||||
@Component({
|
||||
@ -90,7 +90,6 @@ export class ListItemComponent implements OnInit {
|
||||
|
||||
@Output() read: EventEmitter<void> = new EventEmitter<void>();
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly translocoService = inject(TranslocoService);
|
||||
|
||||
actionInProgress: boolean = false;
|
||||
summary: string = '';
|
||||
@ -136,7 +135,7 @@ export class ListItemComponent implements OnInit {
|
||||
performAction(action: ActionItem<any>) {
|
||||
if (action.action == Action.Download) {
|
||||
if (this.downloadInProgress) {
|
||||
this.toastr.info(this.translocoService.translate('toasts.download-in-progress'));
|
||||
this.toastr.info(translate('toasts.download-in-progress'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ import {CommonModule} from "@angular/common";
|
||||
import {CardItemComponent} from "../card-item/card-item.component";
|
||||
import {RelationshipPipe} from "../../pipe/relationship.pipe";
|
||||
import {Device} from "../../_models/device/device";
|
||||
import {TranslocoService} from "@ngneat/transloco";
|
||||
import {translate, TranslocoService} from "@ngneat/transloco";
|
||||
import {SeriesPreviewDrawerComponent} from "../../_single-module/series-preview-drawer/series-preview-drawer.component";
|
||||
|
||||
function deepClone(obj: any): any {
|
||||
@ -96,7 +96,6 @@ export class SeriesCardComponent implements OnInit, OnChanges {
|
||||
actions: ActionItem<Series>[] = [];
|
||||
imageUrl: string = '';
|
||||
|
||||
private readonly translocoService = inject(TranslocoService);
|
||||
private readonly offcanvasService = inject(NgbOffcanvas);
|
||||
|
||||
constructor(private router: Router, private cdRef: ChangeDetectorRef,
|
||||
@ -206,7 +205,7 @@ export class SeriesCardComponent implements OnInit, OnChanges {
|
||||
|
||||
async scanLibrary(series: Series) {
|
||||
this.seriesService.scan(series.libraryId, series.id).subscribe((res: any) => {
|
||||
this.toastr.success(this.translocoService.translate('toasts.scan-queued', {name: series.name}));
|
||||
this.toastr.success(translate('toasts.scan-queued', {name: series.name}));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ import {NgbNav, NgbNavContent, NgbNavItem, NgbNavItemRole, NgbNavLink, NgbNavOut
|
||||
import {
|
||||
SideNavCompanionBarComponent
|
||||
} from '../sidenav/_components/side-nav-companion-bar/side-nav-companion-bar.component';
|
||||
import {TranslocoDirective, TranslocoService} from "@ngneat/transloco";
|
||||
import {TranslocoDirective} from "@ngneat/transloco";
|
||||
import {SeriesFilterV2} from "../_models/metadata/v2/series-filter-v2";
|
||||
import {MetadataService} from "../_services/metadata.service";
|
||||
import {FilterComparison} from "../_models/metadata/v2/filter-comparison";
|
||||
@ -68,11 +68,8 @@ export class LibraryDetailComponent implements OnInit {
|
||||
filterActive: boolean = false;
|
||||
filterActiveCheck!: SeriesFilterV2;
|
||||
refresh: EventEmitter<void> = new EventEmitter();
|
||||
|
||||
jumpKeys: Array<JumpKey> = [];
|
||||
|
||||
translocoService = inject(TranslocoService);
|
||||
|
||||
tabs: Array<{title: string, fragment: string, icon: string}> = [
|
||||
{title: 'library-tab', fragment: '', icon: 'fa-landmark'},
|
||||
{title: 'recommended-tab', fragment: 'recommended', icon: 'fa-award'},
|
||||
|
@ -121,7 +121,10 @@ enum KeyDirection {
|
||||
])
|
||||
],
|
||||
standalone: true,
|
||||
imports: [NgStyle, NgIf, LoadingComponent, SwipeDirective, CanvasRendererComponent, SingleRendererComponent, DoubleRendererComponent, DoubleReverseRendererComponent, DoubleNoCoverRendererComponent, InfiniteScrollerComponent, NgxSliderModule, ReactiveFormsModule, NgFor, NgSwitch, NgSwitchCase, FittingIconPipe, ReaderModeIconPipe, FullscreenIconPipe, TranslocoDirective]
|
||||
imports: [NgStyle, NgIf, LoadingComponent, SwipeDirective, CanvasRendererComponent, SingleRendererComponent,
|
||||
DoubleRendererComponent, DoubleReverseRendererComponent, DoubleNoCoverRendererComponent, InfiniteScrollerComponent,
|
||||
NgxSliderModule, ReactiveFormsModule, NgFor, NgSwitch, NgSwitchCase, FittingIconPipe, ReaderModeIconPipe,
|
||||
FullscreenIconPipe, TranslocoDirective]
|
||||
})
|
||||
export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
@ -382,8 +385,6 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
private pageNumSubject: Subject<{pageNum: number, maxPages: number}> = new ReplaySubject();
|
||||
pageNum$: Observable<{pageNum: number, maxPages: number}> = this.pageNumSubject.asObservable();
|
||||
|
||||
private readonly translocoService = inject(TranslocoService);
|
||||
|
||||
getPageUrl = (pageNum: number, chapterId: number = this.chapterId) => {
|
||||
if (this.bookmarkMode) return this.readerService.getBookmarkPageUrl(this.seriesId, this.user.apiKey, pageNum);
|
||||
return this.readerService.getPageUrl(chapterId, pageNum);
|
||||
@ -591,7 +592,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
this.memberService.hasReadingProgress(this.libraryId).pipe(take(1)).subscribe(progress => {
|
||||
if (!progress) {
|
||||
this.toggleMenu();
|
||||
this.toastr.info(this.translocoService.translate('manga-reader.first-time-reading-manga'));
|
||||
this.toastr.info(translate('manga-reader.first-time-reading-manga'));
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -723,7 +724,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
this.generalSettingsForm.get('layoutMode')?.setValue(LayoutMode.Single);
|
||||
this.generalSettingsForm.get('layoutMode')?.disable();
|
||||
this.toastr.info(this.translocoService.translate('manga-reader.layout-mode-switched'));
|
||||
this.toastr.info(translate('manga-reader.layout-mode-switched'));
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
@ -1223,7 +1224,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
loadNextChapter() {
|
||||
if (this.nextPageDisabled || this.nextChapterDisabled || this.bookmarkMode) {
|
||||
this.toastr.info(this.translocoService.translate('manga-reader.no-next-chapter'));
|
||||
this.toastr.info(translate('manga-reader.no-next-chapter'));
|
||||
this.isLoading = false;
|
||||
this.cdRef.markForCheck();
|
||||
return;
|
||||
@ -1241,7 +1242,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
loadPrevChapter() {
|
||||
if (this.prevPageDisabled || this.prevChapterDisabled || this.bookmarkMode) {
|
||||
this.toastr.info(this.translocoService.translate('manga-reader.no-prev-chapter'));
|
||||
this.toastr.info(translate('manga-reader.no-prev-chapter'));
|
||||
this.isLoading = false;
|
||||
this.cdRef.markForCheck();
|
||||
return;
|
||||
@ -1608,7 +1609,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
this.incognitoMode = false;
|
||||
const newRoute = this.readerService.getNextChapterUrl(this.router.url, this.chapterId, this.incognitoMode, this.readingListMode, this.readingListId);
|
||||
window.history.replaceState({}, '', newRoute);
|
||||
this.toastr.info(this.translocoService.translate('toasts.incognito-off'));
|
||||
this.toastr.info(translate('toasts.incognito-off'));
|
||||
if (!this.bookmarkMode) {
|
||||
this.readerService.saveProgress(this.libraryId, this.seriesId, this.volumeId, this.chapterId, this.pageNum).pipe(take(1)).subscribe(() => {/* No operation */});
|
||||
}
|
||||
@ -1624,7 +1625,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
{key: '↓', description: 'next-page'},
|
||||
{key: 'G', description: 'go-to'},
|
||||
{key: 'B', description: 'bookmark'},
|
||||
{key: this.translocoService.translate('shortcuts-modal.double-click'), description: 'bookmark'},
|
||||
{key: translate('shortcuts-modal.double-click'), description: 'bookmark'},
|
||||
{key: 'ESC', description: 'close-reader'},
|
||||
{key: 'SPACE', description: 'toggle-menu'},
|
||||
];
|
||||
@ -1646,7 +1647,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
data.pageSplitOption = parseInt(modelSettings.pageSplitOption, 10);
|
||||
|
||||
this.accountService.updatePreferences(data).subscribe(updatedPrefs => {
|
||||
this.toastr.success(this.translocoService.translate('manga-reader.user-preferences-updated'));
|
||||
this.toastr.success(translate('manga-reader.user-preferences-updated'));
|
||||
if (this.user) {
|
||||
this.user.preferences = updatedPrefs;
|
||||
this.cdRef.markForCheck();
|
||||
|
@ -21,7 +21,7 @@ import { SeriesService } from 'src/app/_services/series.service';
|
||||
import { ThemeService } from 'src/app/_services/theme.service';
|
||||
import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { NgIf, NgStyle, AsyncPipe } from '@angular/common';
|
||||
import {TranslocoDirective, TranslocoService} from "@ngneat/transloco";
|
||||
import {translate, TranslocoDirective} from "@ngneat/transloco";
|
||||
|
||||
@Component({
|
||||
selector: 'app-pdf-reader',
|
||||
@ -87,8 +87,6 @@ export class PdfReaderComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
bookMode: PageViewModeType = 'multiple';
|
||||
|
||||
private readonly translocoService = inject(TranslocoService);
|
||||
|
||||
constructor(private route: ActivatedRoute, private router: Router, public accountService: AccountService,
|
||||
private seriesService: SeriesService, public readerService: ReaderService,
|
||||
private navService: NavService, private toastr: ToastrService,
|
||||
@ -178,7 +176,7 @@ export class PdfReaderComponent implements OnInit, OnDestroy {
|
||||
this.incognitoMode = false;
|
||||
const newRoute = this.readerService.getNextChapterUrl(this.router.url, this.chapterId, this.incognitoMode, this.readingListMode, this.readingListId);
|
||||
window.history.replaceState({}, '', newRoute);
|
||||
this.toastr.info(this.translocoService.translate('toasts.incognito-off'));
|
||||
this.toastr.info(translate('toasts.incognito-off'));
|
||||
this.saveProgress();
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
@ -2,15 +2,6 @@
|
||||
|
||||
<ng-container *ngIf="items.length > virtualizeAfter; else dragList">
|
||||
<div class="example-list list-group-flush">
|
||||
<!-- <li-virtual-scroll [items]="items" [itemHeight]="itemHeight" [viewCache]="BufferAmount">-->
|
||||
<!-- <div *liVirtualItem="let item; let i=index" class="d-flex list-container">-->
|
||||
<!-- <ng-container [ngTemplateOutlet]="handle" [ngTemplateOutletContext]="{ $implicit: item, idx: i, isVirtualized: true }"></ng-container>-->
|
||||
<!-- <ng-container [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: item, idx: i }"></ng-container>-->
|
||||
|
||||
<!-- <ng-container [ngTemplateOutlet]="removeBtn" [ngTemplateOutletContext]="{$implicit: item, idx: i}"></ng-container>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<!-- </li-virtual-scroll>-->
|
||||
<virtual-scroller #scroll [items]="items" [bufferAmount]="BufferAmount" [parentScroll]="parentScroll">
|
||||
<div class="example-box" *ngFor="let item of scroll.viewPortItems; index as i; trackBy: trackByIdentity">
|
||||
|
||||
@ -46,7 +37,7 @@
|
||||
<ng-container *ngIf="accessibilityMode">
|
||||
<label for="reorder-{{idx}}" class="form-label visually-hidden">{{t('reorder-label')}}</label>
|
||||
<input id="reorder-{{idx}}" class="form-control manual-input" type="number" inputmode="numeric" min="0"
|
||||
[max]="items.length - 1" [value]="idx"
|
||||
[max]="items.length - 1" [value]="item.order"
|
||||
(focusout)="updateIndex(idx, item)" (keydown.enter)="updateIndex(idx, item)" aria-describedby="instructions">
|
||||
</ng-container>
|
||||
<ng-container *ngIf="bulkMode">
|
||||
|
@ -66,8 +66,10 @@ export class DraggableOrderedListComponent {
|
||||
* When enabled, draggability is disabled and a checkbox renders instead of order box or drag handle
|
||||
*/
|
||||
@Input() bulkMode: boolean = false;
|
||||
@Input({required: true}) itemHeight: number = 60;
|
||||
@Input() trackByIdentity: TrackByFunction<any> = (index: number, item: any) => `${item.id}_${item.order}_${item.title}`;
|
||||
/**
|
||||
* After an item is re-ordered, you MUST reload from backend the new data. This is because accessibility mode will use item.order which needs to be in sync.
|
||||
*/
|
||||
@Output() orderUpdated: EventEmitter<IndexUpdateEvent> = new EventEmitter<IndexUpdateEvent>();
|
||||
@Output() itemRemove: EventEmitter<ItemRemoveEvent> = new EventEmitter<ItemRemoveEvent>();
|
||||
@ContentChild('draggableItem') itemTemplate!: TemplateRef<any>;
|
||||
|
@ -127,7 +127,7 @@
|
||||
</ng-template>
|
||||
|
||||
<app-draggable-ordered-list [items]="items" (orderUpdated)="orderUpdated($event)" [accessibilityMode]="accessibilityMode"
|
||||
[showRemoveButton]="false" [itemHeight]="148" [virtualizeAfter]="10">
|
||||
[showRemoveButton]="false">
|
||||
<ng-template #draggableItem let-item let-position="idx">
|
||||
<app-reading-list-item [ngClass]="{'content-container': items.length < 100, 'non-virtualized-container': items.length >= 100}" [item]="item" [position]="position" [libraryTypes]="libraryTypes"
|
||||
[promoted]="item.promoted" (read)="readChapter($event)" (remove)="itemRemoved($event, position)"></app-reading-list-item>
|
||||
|
@ -46,7 +46,11 @@ import {Title} from "@angular/platform-browser";
|
||||
styleUrls: ['./reading-list-detail.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [SideNavCompanionBarComponent, NgIf, CardActionablesComponent, ImageComponent, NgbDropdown, NgbDropdownToggle, NgbDropdownMenu, NgbDropdownItem, ReadMoreComponent, BadgeExpanderComponent, PersonBadgeComponent, A11yClickDirective, LoadingComponent, DraggableOrderedListComponent, ReadingListItemComponent, NgClass, AsyncPipe, DecimalPipe, DatePipe, TranslocoDirective, MetadataDetailComponent]
|
||||
imports: [SideNavCompanionBarComponent, NgIf, CardActionablesComponent, ImageComponent, NgbDropdown,
|
||||
NgbDropdownToggle, NgbDropdownMenu, NgbDropdownItem, ReadMoreComponent, BadgeExpanderComponent,
|
||||
PersonBadgeComponent, A11yClickDirective, LoadingComponent, DraggableOrderedListComponent,
|
||||
ReadingListItemComponent, NgClass, AsyncPipe, DecimalPipe, DatePipe, TranslocoDirective,
|
||||
MetadataDetailComponent]
|
||||
})
|
||||
export class ReadingListDetailComponent implements OnInit {
|
||||
items: Array<ReadingListItem> = [];
|
||||
@ -56,11 +60,7 @@ export class ReadingListDetailComponent implements OnInit {
|
||||
isAdmin: boolean = false;
|
||||
isLoading: boolean = false;
|
||||
accessibilityMode: boolean = false;
|
||||
|
||||
// Downloading
|
||||
hasDownloadingRole: boolean = false;
|
||||
downloadInProgress: boolean = false;
|
||||
|
||||
readingListSummary: string = '';
|
||||
|
||||
libraryTypes: {[key: number]: LibraryType} = {};
|
||||
@ -131,7 +131,7 @@ export class ReadingListDetailComponent implements OnInit {
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
this.readingListService.getListItems(this.listId).subscribe(items => {
|
||||
this.items = items;
|
||||
this.items = [...items];
|
||||
this.isLoading = false;
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
@ -178,7 +178,9 @@ export class ReadingListDetailComponent implements OnInit {
|
||||
|
||||
orderUpdated(event: IndexUpdateEvent) {
|
||||
if (!this.readingList) return;
|
||||
this.readingListService.updatePosition(this.readingList.id, event.item.id, event.fromPosition, event.toPosition).subscribe();
|
||||
this.readingListService.updatePosition(this.readingList.id, event.item.id, event.fromPosition, event.toPosition).subscribe(() => {
|
||||
this.getListItems();
|
||||
});
|
||||
}
|
||||
|
||||
itemRemoved(item: ReadingListItem, position: number) {
|
||||
|
@ -35,6 +35,7 @@
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<a class="btn btn-icon" href="https://wiki.kavitareader.com/en/guides/customization" target="_blank" rel="noopener noreferrer">{{t('help')}}</a>
|
||||
<button type="button" class="btn btn-primary" (click)="close()">{{t('close')}}</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
@ -1 +1,9 @@
|
||||
.modal-body {
|
||||
overflow: hidden;
|
||||
padding: 1rem 0 1rem 1rem;
|
||||
|
||||
.tab-content {
|
||||
max-height: calc(var(--vh) * 100 - 235px);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
<ng-container *transloco="let t; read: 'customize-dashboard-streams'">
|
||||
<app-draggable-ordered-list [items]="items" (orderUpdated)="orderUpdated($event)" [accessibilityMode]="accessibilityMode"
|
||||
[showRemoveButton]="false" [itemHeight]="60">
|
||||
[showRemoveButton]="false">
|
||||
<ng-template #draggableItem let-position="idx" let-item>
|
||||
<app-dashboard-stream-list-item [item]="item" [position]="position" (hide)="updateVisibility($event, position)"></app-dashboard-stream-list-item>
|
||||
</ng-template>
|
||||
|
@ -29,7 +29,6 @@
|
||||
[showRemoveButton]="false" [disabled]="listForm.get('filterSideNavStream')!.value"
|
||||
[bulkMode]="pageOperationsForm.get('bulkMode')!.value"
|
||||
[virtualizeAfter]="virtualizeAfter"
|
||||
[itemHeight]="60"
|
||||
>
|
||||
<ng-template #draggableItem let-position="idx" let-item>
|
||||
<app-sidenav-stream-list-item [item]="item" [position]="position" (hide)="updateVisibility($event, position)"></app-sidenav-stream-list-item>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<ng-container *transloco="let t; read: 'manage-external-sources'">
|
||||
<p>
|
||||
{{t('description')}}
|
||||
<a href="https://wiki.kavitareader.com/en/guides/customization/external-sources" target="_blank" rel="noopener noreferrer">{{t('help-link')}}</a>
|
||||
<a href="https://wiki.kavitareader.com/en/guides/customization#external-source" target="_blank" rel="noopener noreferrer">{{t('help-link')}}</a>
|
||||
</p>
|
||||
|
||||
|
||||
|
@ -219,7 +219,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
||||
this.settingsForm.addControl('noTransitions', new FormControl(this.user.preferences.noTransitions, []));
|
||||
this.settingsForm.addControl('collapseSeriesRelationships', new FormControl(this.user.preferences.collapseSeriesRelationships, []));
|
||||
this.settingsForm.addControl('shareReviews', new FormControl(this.user.preferences.shareReviews, []));
|
||||
this.settingsForm.addControl('locale', new FormControl(this.user.preferences.locale, []));
|
||||
this.settingsForm.addControl('locale', new FormControl(this.user.preferences.locale || 'en', []));
|
||||
|
||||
if (this.locales.length === 1) {
|
||||
this.settingsForm.get('locale')?.disable();
|
||||
|
@ -1753,7 +1753,8 @@
|
||||
"dashboard": "Dashboard",
|
||||
"sidenav": "Side Nav",
|
||||
"external-sources": "External Sources",
|
||||
"smart-filters": "Smart Filters"
|
||||
"smart-filters": "Smart Filters",
|
||||
"help": "{{common.help}}"
|
||||
},
|
||||
|
||||
"customize-dashboard-streams": {
|
||||
|
@ -41,7 +41,7 @@ export function preloadUser(userService: AccountService, transloco: TranslocoSer
|
||||
// If no user or locale is available, fallback to the default language ('en')
|
||||
const localStorageLocale = localStorage.getItem(AccountService.localeKey) || 'en';
|
||||
transloco.setActiveLang(localStorageLocale);
|
||||
return transloco.load(localStorageLocale)
|
||||
return transloco.load(localStorageLocale);
|
||||
})).subscribe();
|
||||
};
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
"name": "GPL-3.0",
|
||||
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
||||
},
|
||||
"version": "0.7.8.12"
|
||||
"version": "0.7.8.13"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user