mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Small Bugfixes (#2414)
This commit is contained in:
parent
26b0cb7d0c
commit
b7e7eaf1a4
@ -50,10 +50,7 @@ public class SeriesServiceTests : AbstractDbTest
|
|||||||
|
|
||||||
public SeriesServiceTests() : base()
|
public SeriesServiceTests() : base()
|
||||||
{
|
{
|
||||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), new FileSystem()
|
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), new FileSystem());
|
||||||
{
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
var locService = new LocalizationService(ds, new MockHostingEnvironment(),
|
var locService = new LocalizationService(ds, new MockHostingEnvironment(),
|
||||||
@ -461,7 +458,7 @@ public class SeriesServiceTests : AbstractDbTest
|
|||||||
|
|
||||||
JobStorage.Current = new InMemoryStorage();
|
JobStorage.Current = new InMemoryStorage();
|
||||||
var ratings = (await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007",
|
var ratings = (await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007",
|
||||||
AppUserIncludes.Ratings))
|
AppUserIncludes.Ratings)!)
|
||||||
.Ratings;
|
.Ratings;
|
||||||
Assert.NotEmpty(ratings);
|
Assert.NotEmpty(ratings);
|
||||||
Assert.Equal(5, ratings.First().Rating);
|
Assert.Equal(5, ratings.First().Rating);
|
||||||
@ -526,6 +523,7 @@ public class SeriesServiceTests : AbstractDbTest
|
|||||||
Assert.True(success);
|
Assert.True(success);
|
||||||
|
|
||||||
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1);
|
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1);
|
||||||
|
Assert.NotNull(series);
|
||||||
Assert.NotNull(series.Metadata);
|
Assert.NotNull(series.Metadata);
|
||||||
Assert.Contains("New Genre".SentenceCase(), series.Metadata.Genres.Select(g => g.Title));
|
Assert.Contains("New Genre".SentenceCase(), series.Metadata.Genres.Select(g => g.Title));
|
||||||
|
|
||||||
@ -805,7 +803,7 @@ public class SeriesServiceTests : AbstractDbTest
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void GetFirstChapterForMetadata_BookWithOnlyVolumeNumbers_Test()
|
public void GetFirstChapterForMetadata_BookWithOnlyVolumeNumbers_Test()
|
||||||
{
|
{
|
||||||
var file = new MangaFileBuilder("Test.cbz", MangaFormat.Archive, 1).Build();
|
var file = new MangaFileBuilder("Test.cbz", MangaFormat.Epub, 1).Build();
|
||||||
|
|
||||||
var series = new SeriesBuilder("Test")
|
var series = new SeriesBuilder("Test")
|
||||||
.WithVolume(new VolumeBuilder("1")
|
.WithVolume(new VolumeBuilder("1")
|
||||||
@ -819,6 +817,7 @@ public class SeriesServiceTests : AbstractDbTest
|
|||||||
series.Library = new LibraryBuilder("Test LIb", LibraryType.Book).Build();
|
series.Library = new LibraryBuilder("Test LIb", LibraryType.Book).Build();
|
||||||
|
|
||||||
var firstChapter = SeriesService.GetFirstChapterForMetadata(series);
|
var firstChapter = SeriesService.GetFirstChapterForMetadata(series);
|
||||||
|
Assert.NotNull(firstChapter);
|
||||||
Assert.Equal(1, firstChapter.Pages);
|
Assert.Equal(1, firstChapter.Pages);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -828,6 +827,7 @@ public class SeriesServiceTests : AbstractDbTest
|
|||||||
var series = CreateSeriesMock();
|
var series = CreateSeriesMock();
|
||||||
|
|
||||||
var firstChapter = SeriesService.GetFirstChapterForMetadata(series);
|
var firstChapter = SeriesService.GetFirstChapterForMetadata(series);
|
||||||
|
Assert.NotNull(firstChapter);
|
||||||
Assert.Same("1", firstChapter.Range);
|
Assert.Same("1", firstChapter.Range);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ public static class Parser
|
|||||||
public const string DefaultVolume = "0";
|
public const string DefaultVolume = "0";
|
||||||
public static readonly TimeSpan RegexTimeout = TimeSpan.FromMilliseconds(500);
|
public static readonly TimeSpan RegexTimeout = TimeSpan.FromMilliseconds(500);
|
||||||
|
|
||||||
public const string ImageFileExtensions = @"^(\.png|\.jpeg|\.jpg|\.webp|\.gif|\.avif)";
|
public const string ImageFileExtensions = @"^(\.png|\.jpeg|\.jpg|\.webp|\.gif|\.avif)"; // Don't forget to update CoverChooser
|
||||||
public const string ArchiveFileExtensions = @"\.cbz|\.zip|\.rar|\.cbr|\.tar.gz|\.7zip|\.7z|\.cb7|\.cbt";
|
public const string ArchiveFileExtensions = @"\.cbz|\.zip|\.rar|\.cbr|\.tar.gz|\.7zip|\.7z|\.cb7|\.cbt";
|
||||||
private const string BookFileExtensions = @"\.epub|\.pdf";
|
private const string BookFileExtensions = @"\.epub|\.pdf";
|
||||||
private const string XmlRegexExtensions = @"\.xml";
|
private const string XmlRegexExtensions = @"\.xml";
|
||||||
|
@ -309,7 +309,7 @@ public class ProcessSeries : IProcessSeries
|
|||||||
if (series.Metadata.MaxCount == series.Metadata.TotalCount && series.Metadata.TotalCount > 0)
|
if (series.Metadata.MaxCount == series.Metadata.TotalCount && series.Metadata.TotalCount > 0)
|
||||||
{
|
{
|
||||||
series.Metadata.PublicationStatus = PublicationStatus.Completed;
|
series.Metadata.PublicationStatus = PublicationStatus.Completed;
|
||||||
} else if (series.Metadata.TotalCount > 0)
|
} else if (series.Metadata.TotalCount > 0 && series.Metadata.MaxCount > 0)
|
||||||
{
|
{
|
||||||
series.Metadata.PublicationStatus = PublicationStatus.Ended;
|
series.Metadata.PublicationStatus = PublicationStatus.Ended;
|
||||||
}
|
}
|
||||||
|
@ -29,3 +29,7 @@ Run `npx playwright test --reporter=line` or `npx playwright test` to run e2e te
|
|||||||
|
|
||||||
ng serve --host 0.0.0.0
|
ng serve --host 0.0.0.0
|
||||||
and update environment.ts to your local ip.
|
and update environment.ts to your local ip.
|
||||||
|
|
||||||
|
## Notes:
|
||||||
|
- injected services should be at the top of the file
|
||||||
|
- all components must be standalone
|
||||||
|
25
UI/Web/package-lock.json
generated
25
UI/Web/package-lock.json
generated
@ -33,12 +33,8 @@
|
|||||||
"@popperjs/core": "^2.11.7",
|
"@popperjs/core": "^2.11.7",
|
||||||
"@swimlane/ngx-charts": "^20.1.2",
|
"@swimlane/ngx-charts": "^20.1.2",
|
||||||
"@tweenjs/tween.js": "^21.0.0",
|
"@tweenjs/tween.js": "^21.0.0",
|
||||||
"@types/file-saver": "^2.0.6",
|
|
||||||
"angular-animations": "^0.11.0",
|
|
||||||
"bootstrap": "^5.3.1",
|
"bootstrap": "^5.3.1",
|
||||||
"charts.css": "^1.1.0",
|
"charts.css": "^1.1.0",
|
||||||
"eventsource": "^2.0.2",
|
|
||||||
"file-saver": "^2.0.5",
|
|
||||||
"luxon": "^3.4.3",
|
"luxon": "^3.4.3",
|
||||||
"ng-circle-progress": "^1.7.1",
|
"ng-circle-progress": "^1.7.1",
|
||||||
"ng-lazyload-image": "^9.1.3",
|
"ng-lazyload-image": "^9.1.3",
|
||||||
@ -4785,11 +4781,6 @@
|
|||||||
"@types/send": "*"
|
"@types/send": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/file-saver": {
|
|
||||||
"version": "2.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.6.tgz",
|
|
||||||
"integrity": "sha512-Mw671DVqoMHbjw0w4v2iiOro01dlT/WhWp5uwecBa0Wg8c+bcZOjgF1ndBnlaxhtvFCgTRBtsGivSVhrK/vnag=="
|
|
||||||
},
|
|
||||||
"node_modules/@types/geojson": {
|
"node_modules/@types/geojson": {
|
||||||
"version": "7946.0.10",
|
"version": "7946.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz",
|
||||||
@ -5894,17 +5885,6 @@
|
|||||||
"ajv": "^8.8.2"
|
"ajv": "^8.8.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/angular-animations": {
|
|
||||||
"version": "0.11.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/angular-animations/-/angular-animations-0.11.0.tgz",
|
|
||||||
"integrity": "sha512-P2RuOe+T97bhgGDLtOYK9V45QA5y+kFUxoJfRAua8Ymo0bI5lWyw8oiVmBoEIZUU+nooYoJvQXgVKuZJA7/z3g==",
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "^2.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@angular/animations": ">=6.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ansi-colors": {
|
"node_modules/ansi-colors": {
|
||||||
"version": "4.1.3",
|
"version": "4.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
|
||||||
@ -8560,11 +8540,6 @@
|
|||||||
"node": "^10.12.0 || >=12.0.0"
|
"node": "^10.12.0 || >=12.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/file-saver": {
|
|
||||||
"version": "2.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
|
|
||||||
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
|
|
||||||
},
|
|
||||||
"node_modules/filelist": {
|
"node_modules/filelist": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
|
||||||
|
@ -38,12 +38,8 @@
|
|||||||
"@popperjs/core": "^2.11.7",
|
"@popperjs/core": "^2.11.7",
|
||||||
"@swimlane/ngx-charts": "^20.1.2",
|
"@swimlane/ngx-charts": "^20.1.2",
|
||||||
"@tweenjs/tween.js": "^21.0.0",
|
"@tweenjs/tween.js": "^21.0.0",
|
||||||
"@types/file-saver": "^2.0.6",
|
|
||||||
"angular-animations": "^0.11.0",
|
|
||||||
"bootstrap": "^5.3.1",
|
"bootstrap": "^5.3.1",
|
||||||
"charts.css": "^1.1.0",
|
"charts.css": "^1.1.0",
|
||||||
"eventsource": "^2.0.2",
|
|
||||||
"file-saver": "^2.0.5",
|
|
||||||
"luxon": "^3.4.3",
|
"luxon": "^3.4.3",
|
||||||
"ng-circle-progress": "^1.7.1",
|
"ng-circle-progress": "^1.7.1",
|
||||||
"ng-lazyload-image": "^9.1.3",
|
"ng-lazyload-image": "^9.1.3",
|
||||||
|
@ -99,13 +99,9 @@
|
|||||||
<li [ngbNavItem]="tabs[TabID.Cover]" [disabled]="(isAdmin$ | async) === false">
|
<li [ngbNavItem]="tabs[TabID.Cover]" [disabled]="(isAdmin$ | async) === false">
|
||||||
<a ngbNavLink>{{t(tabs[TabID.Cover].title)}}</a>
|
<a ngbNavLink>{{t(tabs[TabID.Cover].title)}}</a>
|
||||||
<ng-template ngbNavContent>
|
<ng-template ngbNavContent>
|
||||||
<app-cover-image-chooser [(imageUrls)]="imageUrls"
|
<app-cover-image-chooser [(imageUrls)]="imageUrls" (imageSelected)="updateCoverImageIndex($event)"
|
||||||
[showReset]="chapter.coverImageLocked"
|
(selectedBase64Url)="applyCoverImage($event)" [showReset]="chapter.coverImageLocked"
|
||||||
[showApplyButton]="true"
|
(resetClicked)="resetCoverImage()"></app-cover-image-chooser>
|
||||||
(applyCover)="applyCoverImage($event)"
|
|
||||||
(resetCover)="resetCoverImage()"
|
|
||||||
>
|
|
||||||
</app-cover-image-chooser>
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
@ -206,6 +206,11 @@ export class CardDetailDrawerComponent implements OnInit {
|
|||||||
this.uploadService.updateChapterCoverImage(this.chapter.id, coverUrl).subscribe(() => {});
|
this.uploadService.updateChapterCoverImage(this.chapter.id, coverUrl).subscribe(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateCoverImageIndex(selectedIndex: number) {
|
||||||
|
if (selectedIndex <= 0) return;
|
||||||
|
this.applyCoverImage(this.imageUrls[selectedIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
resetCoverImage() {
|
resetCoverImage() {
|
||||||
this.uploadService.resetChapterCoverLock(this.chapter.id).subscribe(() => {
|
this.uploadService.resetChapterCoverLock(this.chapter.id).subscribe(() => {
|
||||||
this.toastr.info(translate('toasts.regen-cover'));
|
this.toastr.info(translate('toasts.regen-cover'));
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
<div class="input-group col-auto me-md-2" style="width: 83%">
|
<div class="input-group col-auto me-md-2" style="width: 83%">
|
||||||
<label class="input-group-text" for="load-image">{{t('url-label')}}</label>
|
<label class="input-group-text" for="load-image">{{t('url-label')}}</label>
|
||||||
<input type="text" autofocus autocomplete="off" class="form-control" formControlName="coverImageUrl" placeholder="https://" id="load-image" class="form-control">
|
<input type="text" autofocus autocomplete="off" class="form-control" formControlName="coverImageUrl" placeholder="https://" id="load-image" class="form-control">
|
||||||
<button class="btn btn-outline-secondary" type="button" id="load-image-addon" (click)="loadImage(); mode='all';" [disabled]="(form.get('coverImageUrl')?.value).length === 0">
|
<button class="btn btn-outline-secondary" type="button" id="load-image-addon" (click)="loadImageFromUrl(); mode='all';" [disabled]="(form.get('coverImageUrl')?.value).length === 0">
|
||||||
{{t('load')}}
|
{{t('load')}}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
Component, DestroyRef,
|
Component,
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
inject,
|
inject,
|
||||||
Inject,
|
Inject,
|
||||||
@ -11,7 +11,7 @@ import {
|
|||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import {FormBuilder, FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';
|
import {FormBuilder, FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';
|
||||||
import {NgxFileDropEntry, FileSystemFileEntry, NgxFileDropModule} from 'ngx-file-drop';
|
import {NgxFileDropEntry, FileSystemFileEntry, NgxFileDropModule} from 'ngx-file-drop';
|
||||||
import { fromEvent, Subject } from 'rxjs';
|
import { fromEvent } from 'rxjs';
|
||||||
import { takeWhile } from 'rxjs/operators';
|
import { takeWhile } from 'rxjs/operators';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
import { ImageService } from 'src/app/_services/image.service';
|
import { ImageService } from 'src/app/_services/image.service';
|
||||||
@ -37,7 +37,6 @@ import {translate, TranslocoModule} from "@ngneat/transloco";
|
|||||||
})
|
})
|
||||||
export class CoverImageChooserComponent implements OnInit {
|
export class CoverImageChooserComponent implements OnInit {
|
||||||
|
|
||||||
private readonly destroyRef = inject(DestroyRef);
|
|
||||||
private readonly cdRef = inject(ChangeDetectorRef);
|
private readonly cdRef = inject(ChangeDetectorRef);
|
||||||
public readonly imageService = inject(ImageService);
|
public readonly imageService = inject(ImageService);
|
||||||
public readonly fb = inject(FormBuilder);
|
public readonly fb = inject(FormBuilder);
|
||||||
@ -84,8 +83,7 @@ export class CoverImageChooserComponent implements OnInit {
|
|||||||
appliedIndex: number = 0;
|
appliedIndex: number = 0;
|
||||||
form!: FormGroup;
|
form!: FormGroup;
|
||||||
files: NgxFileDropEntry[] = [];
|
files: NgxFileDropEntry[] = [];
|
||||||
acceptableExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.webp'].join(',');
|
acceptableExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.avif'].join(',');
|
||||||
|
|
||||||
mode: 'file' | 'url' | 'all' = 'all';
|
mode: 'file' | 'url' | 'all' = 'all';
|
||||||
|
|
||||||
constructor(@Inject(DOCUMENT) private document: Document) { }
|
constructor(@Inject(DOCUMENT) private document: Document) { }
|
||||||
@ -182,6 +180,25 @@ export class CoverImageChooserComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadImageFromUrl(url?: string) {
|
||||||
|
url = url || this.form.get('coverImageUrl')?.value.trim();
|
||||||
|
if (!url || url === '') return;
|
||||||
|
|
||||||
|
this.uploadService.uploadByUrl(url).subscribe(filename => {
|
||||||
|
const img = new Image();
|
||||||
|
img.crossOrigin = 'Anonymous';
|
||||||
|
img.src = this.imageService.getCoverUploadImage(filename);
|
||||||
|
img.onload = (e) => this.handleUrlImageAdd(img);
|
||||||
|
img.onerror = (e) => {
|
||||||
|
this.toastr.error(translate('errors.rejected-cover-upload'));
|
||||||
|
this.form.get('coverImageUrl')?.setValue('');
|
||||||
|
this.cdRef.markForCheck();
|
||||||
|
};
|
||||||
|
this.form.get('coverImageUrl')?.setValue('');
|
||||||
|
this.cdRef.markForCheck();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
changeMode(mode: 'url') {
|
changeMode(mode: 'url') {
|
||||||
@ -239,12 +256,6 @@ export class CoverImageChooserComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public fileOver(event: any){
|
|
||||||
}
|
|
||||||
|
|
||||||
public fileLeave(event: any){
|
|
||||||
}
|
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.resetClicked.emit();
|
this.resetClicked.emit();
|
||||||
this.selectedIndex = -1;
|
this.selectedIndex = -1;
|
||||||
@ -275,4 +286,6 @@ export class CoverImageChooserComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected fileOver(event: any){}
|
||||||
|
protected fileLeave(event: any){}
|
||||||
}
|
}
|
||||||
|
@ -157,7 +157,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
/**
|
/**
|
||||||
* Debug mode. Will show extra information. Use bitwise (|) operators between different modes to enable different output
|
* Debug mode. Will show extra information. Use bitwise (|) operators between different modes to enable different output
|
||||||
*/
|
*/
|
||||||
debugMode: DEBUG_MODES = DEBUG_MODES.Outline;
|
debugMode: DEBUG_MODES = DEBUG_MODES.None;
|
||||||
/**
|
/**
|
||||||
* Debug mode. Will filter out any messages in here so they don't hit the log
|
* Debug mode. Will filter out any messages in here so they don't hit the log
|
||||||
*/
|
*/
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<h5>{{heading}}</h5>
|
<h5>{{heading}}</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-9 col-md-8 col-sm-12">
|
<div class="col-lg-9 col-md-8 col-sm-12">
|
||||||
<app-badge-expander [items]="tags">
|
<app-badge-expander [items]="tags" [itemsTillExpander]="utilityService.getActiveBreakpoint() >= Breakpoint.Desktop ? 30 : 4">
|
||||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||||
<ng-container *ngIf="itemTemplate; else useTitle">
|
<ng-container *ngIf="itemTemplate; else useTitle">
|
||||||
<span (click)="goTo(queryParam, item.id)">
|
<span (click)="goTo(queryParam, item.id)">
|
||||||
|
@ -6,6 +6,7 @@ import {TagBadgeComponent, TagBadgeCursor} from "../../../shared/tag-badge/tag-b
|
|||||||
import {FilterUtilitiesService} from "../../../shared/_services/filter-utilities.service";
|
import {FilterUtilitiesService} from "../../../shared/_services/filter-utilities.service";
|
||||||
import {FilterComparison} from "../../../_models/metadata/v2/filter-comparison";
|
import {FilterComparison} from "../../../_models/metadata/v2/filter-comparison";
|
||||||
import {FilterField} from "../../../_models/metadata/v2/filter-field";
|
import {FilterField} from "../../../_models/metadata/v2/filter-field";
|
||||||
|
import {Breakpoint, UtilityService} from "../../../shared/_services/utility.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-metadata-detail',
|
selector: 'app-metadata-detail',
|
||||||
@ -17,6 +18,11 @@ import {FilterField} from "../../../_models/metadata/v2/filter-field";
|
|||||||
})
|
})
|
||||||
export class MetadataDetailComponent {
|
export class MetadataDetailComponent {
|
||||||
|
|
||||||
|
private readonly filterUtilityService = inject(FilterUtilitiesService);
|
||||||
|
public readonly utilityService = inject(UtilityService);
|
||||||
|
protected readonly TagBadgeCursor = TagBadgeCursor;
|
||||||
|
protected readonly Breakpoint = Breakpoint;
|
||||||
|
|
||||||
@Input({required: true}) tags: Array<any> = [];
|
@Input({required: true}) tags: Array<any> = [];
|
||||||
@Input({required: true}) libraryId!: number;
|
@Input({required: true}) libraryId!: number;
|
||||||
@Input({required: true}) heading!: string;
|
@Input({required: true}) heading!: string;
|
||||||
@ -24,12 +30,11 @@ export class MetadataDetailComponent {
|
|||||||
@ContentChild('titleTemplate') titleTemplate!: TemplateRef<any>;
|
@ContentChild('titleTemplate') titleTemplate!: TemplateRef<any>;
|
||||||
@ContentChild('itemTemplate') itemTemplate?: TemplateRef<any>;
|
@ContentChild('itemTemplate') itemTemplate?: TemplateRef<any>;
|
||||||
|
|
||||||
private readonly filterUtilityService = inject(FilterUtilitiesService);
|
|
||||||
protected readonly TagBadgeCursor = TagBadgeCursor;
|
|
||||||
|
|
||||||
|
|
||||||
goTo(queryParamName: FilterField, filter: any) {
|
goTo(queryParamName: FilterField, filter: any) {
|
||||||
if (queryParamName === FilterField.None) return;
|
if (queryParamName === FilterField.None) return;
|
||||||
this.filterUtilityService.applyFilter(['library', this.libraryId], queryParamName, FilterComparison.Equal, filter).subscribe();
|
this.filterUtilityService.applyFilter(['library', this.libraryId], queryParamName, FilterComparison.Equal, filter).subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<ng-container *transloco="let t; read: 'series-metadata-detail'">
|
<ng-container *transloco="let t; read: 'series-metadata-detail'">
|
||||||
<div class="row g-0 mt-2 mb-2">
|
<div class="row g-0 mt-2 mb-2">
|
||||||
<app-read-more [text]="seriesSummary" [maxLength]="250"></app-read-more>
|
<app-read-more [text]="seriesSummary" [maxLength]="utilityService.getActiveBreakpoint() >= Breakpoint.Desktop ? 1000 : 250"></app-read-more>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
@ -121,4 +121,6 @@ export class SeriesMetadataDetailComponent implements OnChanges {
|
|||||||
navigate(basePage: string, id: number) {
|
navigate(basePage: string, id: number) {
|
||||||
this.router.navigate([basePage, id]);
|
this.router.navigate([basePage, id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected readonly Breakpoint = Breakpoint;
|
||||||
}
|
}
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
import {InjectionToken} from '@angular/core'
|
|
||||||
import { saveAs } from 'file-saver';
|
|
||||||
|
|
||||||
export type Saver = (blob: Blob, filename?: string) => void
|
|
||||||
|
|
||||||
export const SAVER = new InjectionToken<Saver>('saver')
|
|
||||||
|
|
||||||
export function getSaver(): Saver {
|
|
||||||
return saveAs;
|
|
||||||
}
|
|
@ -14,7 +14,6 @@ import {
|
|||||||
of,
|
of,
|
||||||
filter,
|
filter,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import { SAVER, Saver } from '../_providers/saver.provider';
|
|
||||||
import { download, Download } from '../_models/download';
|
import { download, Download } from '../_models/download';
|
||||||
import { PageBookmark } from 'src/app/_models/readers/page-bookmark';
|
import { PageBookmark } from 'src/app/_models/readers/page-bookmark';
|
||||||
import {switchMap, take, takeWhile, throttleTime} from 'rxjs/operators';
|
import {switchMap, take, takeWhile, throttleTime} from 'rxjs/operators';
|
||||||
@ -69,7 +68,7 @@ export class DownloadService {
|
|||||||
private readonly destroyRef = inject(DestroyRef);
|
private readonly destroyRef = inject(DestroyRef);
|
||||||
|
|
||||||
constructor(private httpClient: HttpClient, private confirmService: ConfirmService,
|
constructor(private httpClient: HttpClient, private confirmService: ConfirmService,
|
||||||
@Inject(SAVER) private save: Saver, private accountService: AccountService) { }
|
private accountService: AccountService) { }
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -270,4 +269,15 @@ export class DownloadService {
|
|||||||
finalize(() => this.finalizeDownloadState(downloadType, subtitle))
|
finalize(() => this.finalizeDownloadState(downloadType, subtitle))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private save(blob: Blob, filename: string) {
|
||||||
|
const saveLink = document.createElement( 'a' );
|
||||||
|
if (saveLink.href) {
|
||||||
|
URL.revokeObjectURL(saveLink.href);
|
||||||
|
}
|
||||||
|
saveLink.href = URL.createObjectURL(blob);
|
||||||
|
saveLink.download = filename;
|
||||||
|
saveLink.dispatchEvent( new MouseEvent( 'click' ) );
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,13 @@
|
|||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, Input, OnInit, TemplateRef } from '@angular/core';
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Component,
|
||||||
|
ContentChild,
|
||||||
|
inject,
|
||||||
|
Input,
|
||||||
|
OnInit,
|
||||||
|
TemplateRef
|
||||||
|
} from '@angular/core';
|
||||||
import {CommonModule} from "@angular/common";
|
import {CommonModule} from "@angular/common";
|
||||||
import {TranslocoDirective} from "@ngneat/transloco";
|
import {TranslocoDirective} from "@ngneat/transloco";
|
||||||
|
|
||||||
@ -12,6 +21,8 @@ import {TranslocoDirective} from "@ngneat/transloco";
|
|||||||
})
|
})
|
||||||
export class BadgeExpanderComponent implements OnInit {
|
export class BadgeExpanderComponent implements OnInit {
|
||||||
|
|
||||||
|
private readonly cdRef = inject(ChangeDetectorRef);
|
||||||
|
|
||||||
@Input() items: Array<any> = [];
|
@Input() items: Array<any> = [];
|
||||||
@Input() itemsTillExpander: number = 4;
|
@Input() itemsTillExpander: number = 4;
|
||||||
@ContentChild('badgeExpanderItem') itemTemplate!: TemplateRef<any>;
|
@ContentChild('badgeExpanderItem') itemTemplate!: TemplateRef<any>;
|
||||||
@ -24,8 +35,6 @@ export class BadgeExpanderComponent implements OnInit {
|
|||||||
return Math.max(this.items.length - this.itemsTillExpander, 0);
|
return Math.max(this.items.length - this.itemsTillExpander, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(private readonly cdRef: ChangeDetectorRef) { }
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.visibleItems = this.items.slice(0, this.itemsTillExpander);
|
this.visibleItems = this.items.slice(0, this.itemsTillExpander);
|
||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
|
@ -79,13 +79,9 @@
|
|||||||
<ng-template ngbNavContent>
|
<ng-template ngbNavContent>
|
||||||
<p *ngIf="isAddLibrary" class="alert alert-secondary" role="alert">{{t('cover-description')}}</p>
|
<p *ngIf="isAddLibrary" class="alert alert-secondary" role="alert">{{t('cover-description')}}</p>
|
||||||
<p>{{t('cover-description-extra')}}</p>
|
<p>{{t('cover-description-extra')}}</p>
|
||||||
<app-cover-image-chooser [(imageUrls)]="imageUrls"
|
<app-cover-image-chooser [(imageUrls)]="imageUrls" (imageSelected)="updateCoverImageIndex($event)"
|
||||||
[showReset]="false"
|
(selectedBase64Url)="applyCoverImage($event)" [showReset]="library.coverImage !== undefined "
|
||||||
[showApplyButton]="true"
|
(resetClicked)="resetCoverImage()"></app-cover-image-chooser>
|
||||||
(applyCover)="applyCoverImage($event)"
|
|
||||||
(resetCover)="resetCoverImage()"
|
|
||||||
>
|
|
||||||
</app-cover-image-chooser>
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
@ -218,6 +218,11 @@ export class LibrarySettingsModalComponent implements OnInit {
|
|||||||
this.uploadService.updateLibraryCoverImage(this.library.id, coverUrl).subscribe(() => {});
|
this.uploadService.updateLibraryCoverImage(this.library.id, coverUrl).subscribe(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateCoverImageIndex(selectedIndex: number) {
|
||||||
|
if (selectedIndex <= 0) return;
|
||||||
|
this.applyCoverImage(this.imageUrls[selectedIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
resetCoverImage() {
|
resetCoverImage() {
|
||||||
this.uploadService.updateLibraryCoverImage(this.library.id, '').subscribe(() => {});
|
this.uploadService.updateLibraryCoverImage(this.library.id, '').subscribe(() => {});
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ import { NgCircleProgressModule } from 'ng-circle-progress';
|
|||||||
import { ToastrModule } from 'ngx-toastr';
|
import { ToastrModule } from 'ngx-toastr';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { AppRoutingModule } from './app/app-routing.module';
|
import { AppRoutingModule } from './app/app-routing.module';
|
||||||
import { SAVER, getSaver } from './app/shared/_providers/saver.provider';
|
|
||||||
import { Title, BrowserModule, bootstrapApplication } from '@angular/platform-browser';
|
import { Title, BrowserModule, bootstrapApplication } from '@angular/platform-browser';
|
||||||
import { JwtInterceptor } from './app/_interceptors/jwt.interceptor';
|
import { JwtInterceptor } from './app/_interceptors/jwt.interceptor';
|
||||||
import { ErrorInterceptor } from './app/_interceptors/error.interceptor';
|
import { ErrorInterceptor } from './app/_interceptors/error.interceptor';
|
||||||
@ -147,7 +146,6 @@ bootstrapApplication(AppComponent, {
|
|||||||
{ provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
|
{ provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
|
||||||
preLoad,
|
preLoad,
|
||||||
Title,
|
Title,
|
||||||
{ provide: SAVER, useFactory: getSaver },
|
|
||||||
provideHttpClient(withInterceptorsFromDi())
|
provideHttpClient(withInterceptorsFromDi())
|
||||||
]
|
]
|
||||||
} as ApplicationConfig)
|
} as ApplicationConfig)
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"name": "GPL-3.0",
|
"name": "GPL-3.0",
|
||||||
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
||||||
},
|
},
|
||||||
"version": "0.7.10.5"
|
"version": "0.7.10.6"
|
||||||
},
|
},
|
||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user