.kavitaignore no more (#2442)

This commit is contained in:
Joe Milazzo 2023-11-19 12:15:32 -06:00 committed by GitHub
parent cd27efecdd
commit 7221501c4d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
91 changed files with 5968 additions and 1026 deletions

View file

@ -1,5 +1,5 @@
import { AgeRestriction } from '../metadata/age-restriction';
import { Library } from '../library';
import { Library } from '../library/library';
export interface Member {
id: number;

View file

@ -0,0 +1,10 @@
export enum FileTypeGroup {
Archive = 1,
Epub = 2,
Pdf = 3,
Images = 4
}
export const allFileTypeGroup = Object.keys(FileTypeGroup)
.filter(key => !isNaN(Number(key)) && parseInt(key, 10) >= 0)
.map(key => parseInt(key, 10)) as FileTypeGroup[];

View file

@ -1,7 +1,10 @@
import {FileTypeGroup} from "./file-type-group.enum";
export enum LibraryType {
Manga = 0,
Comic = 1,
Book = 2,
Images = 3
}
export interface Library {
@ -19,4 +22,6 @@ export interface Library {
manageReadingLists: boolean;
allowScrobbling: boolean;
collapseSeriesRelationships: boolean;
libraryFileTypes: Array<FileTypeGroup>;
excludePatterns: Array<string>;
}

View file

@ -1,5 +1,5 @@
import { FileDimension } from "src/app/manga-reader/_models/file-dimension";
import { LibraryType } from "../library";
import { LibraryType } from "../library/library";
import { MangaFormat } from "../manga-format";
export interface BookmarkInfo {
@ -17,4 +17,4 @@ export interface BookmarkInfo {
* This will not always be present. Depends on if asked from backend.
*/
doublePairs?: {[key: number]: number};
}
}

View file

@ -1,4 +1,4 @@
import { LibraryType } from "./library";
import { LibraryType } from "./library/library";
import { MangaFormat } from "./manga-format";
export interface ReadingListItem {
@ -27,11 +27,11 @@ export interface ReadingList {
coverImageLocked: boolean;
items: Array<ReadingListItem>;
/**
* If this is empty or null, the cover image isn't set. Do not use this externally.
* If this is empty or null, the cover image isn't set. Do not use this externally.
*/
coverImage: string;
startingYear: number;
startingMonth: number;
endingYear: number;
endingMonth: number;
}
}

View file

@ -1,4 +1,4 @@
import { LibraryType } from "./library";
import { LibraryType } from "./library/library";
export interface RecentlyAddedItem {
seriesId: number;
@ -8,6 +8,6 @@ export interface RecentlyAddedItem {
libraryId: number;
libraryType: LibraryType;
volumeId: number;
chapterId: number;
chapterId: number;
id: number; // This is UI only, sent from backend but has no relation to any entity
}
}

View file

@ -1,5 +1,5 @@
import { Chapter } from "../chapter";
import { Library } from "../library";
import { Library } from "../library/library";
import { MangaFile } from "../manga-file";
import { SearchResult } from "./search-result";
import { Tag } from "../tag";
@ -24,6 +24,6 @@ export class SearchResultGroup {
this.genres = [];
this.tags = [];
this.files = [];
this.chapters = [];
this.chapters = [];
}
}
}

View file

@ -1,4 +1,4 @@
import { LibraryType } from "./library";
import { LibraryType } from "./library/library";
export interface SeriesGroup {
seriesId: number;
@ -8,7 +8,7 @@ export interface SeriesGroup {
libraryId: number;
libraryType: LibraryType;
volumeId: number;
chapterId: number;
chapterId: number;
id: number; // This is UI only, sent from backend but has no relation to any entity
count: number;
}
count: number;
}

View file

@ -1,5 +1,5 @@
import {SideNavStreamType} from "./sidenav-stream-type.enum";
import {Library, LibraryType} from "../library";
import {Library, LibraryType} from "../library/library";
import {CommonStream} from "../common-stream";
import {ExternalSource} from "./external-source";

View file

@ -0,0 +1,25 @@
import { Pipe, PipeTransform } from '@angular/core';
import {FileTypeGroup} from "../_models/library/file-type-group.enum";
import {translate} from "@ngneat/transloco";
@Pipe({
name: 'fileTypeGroup',
standalone: true
})
export class FileTypeGroupPipe implements PipeTransform {
transform(value: FileTypeGroup): string {
switch (value) {
case FileTypeGroup.Archive:
return translate('file-type-group-pipe.archive');
case FileTypeGroup.Epub:
return translate('file-type-group-pipe.epub');
case FileTypeGroup.Pdf:
return translate('file-type-group-pipe.pdf');
case FileTypeGroup.Images:
return translate('file-type-group-pipe.image');
}
}
}

View file

@ -1,5 +1,5 @@
import {inject, Pipe, PipeTransform} from '@angular/core';
import { LibraryType } from '../_models/library';
import { LibraryType } from '../_models/library/library';
import {TranslocoService} from "@ngneat/transloco";
/**

View file

@ -3,7 +3,7 @@ import { map, Observable, shareReplay } from 'rxjs';
import { Chapter } from '../_models/chapter';
import { CollectionTag } from '../_models/collection-tag';
import { Device } from '../_models/device/device';
import { Library } from '../_models/library';
import { Library } from '../_models/library/library';
import { ReadingList } from '../_models/reading-list';
import { Series } from '../_models/series';
import { Volume } from '../_models/volume';

View file

@ -10,7 +10,7 @@ import { ConfirmService } from '../shared/confirm.service';
import { LibrarySettingsModalComponent } from '../sidenav/_modals/library-settings-modal/library-settings-modal.component';
import { Chapter } from '../_models/chapter';
import { Device } from '../_models/device/device';
import { Library } from '../_models/library';
import { Library } from '../_models/library/library';
import { ReadingList } from '../_models/reading-list';
import { Series } from '../_models/series';
import { Volume } from '../_models/volume';

View file

@ -1,8 +1,8 @@
import { Injectable } from '@angular/core';
import {environment} from "../environments/environment";
import {environment} from "../../environments/environment";
import {HttpClient} from "@angular/common/http";
import {ExternalSource} from "./_models/sidenav/external-source";
import {TextResonse} from "./_types/text-response";
import {ExternalSource} from "../_models/sidenav/external-source";
import {TextResonse} from "../_types/text-response";
import {map} from "rxjs/operators";
@Injectable({

View file

@ -4,7 +4,7 @@ import { of } from 'rxjs';
import { map } from 'rxjs/operators';
import { environment } from 'src/environments/environment';
import { JumpKey } from '../_models/jumpbar/jump-key';
import { Library, LibraryType } from '../_models/library';
import { Library, LibraryType } from '../_models/library/library';
import { DirectoryDto } from '../_models/system/directory-dto';

View file

@ -1,6 +1,6 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, Input, OnInit} from '@angular/core';
import {NgbActiveModal} from '@ng-bootstrap/ng-bootstrap';
import {Library} from 'src/app/_models/library';
import {Library} from 'src/app/_models/library/library';
import {Member} from 'src/app/_models/auth/member';
import {LibraryService} from 'src/app/_services/library.service';
import {SelectionModel} from 'src/app/typeahead/_components/typeahead.component';

View file

@ -2,7 +2,7 @@ import { Component, Input, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators, ReactiveFormsModule } from '@angular/forms';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { AgeRestriction } from 'src/app/_models/metadata/age-restriction';
import { Library } from 'src/app/_models/library';
import { Library } from 'src/app/_models/library/library';
import { Member } from 'src/app/_models/auth/member';
import { AccountService } from 'src/app/_services/account.service';
import { SentenceCasePipe } from '../../_pipes/sentence-case.pipe';

View file

@ -4,7 +4,7 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { ToastrService } from 'ngx-toastr';
import { AgeRestriction } from 'src/app/_models/metadata/age-restriction';
import { InviteUserResponse } from 'src/app/_models/auth/invite-user-response';
import { Library } from 'src/app/_models/library';
import { Library } from 'src/app/_models/library/library';
import { AgeRating } from 'src/app/_models/metadata/age-rating';
import { AccountService } from 'src/app/_services/account.service';
import { ApiKeyComponent } from '../../user-settings/api-key/api-key.component';

View file

@ -1,6 +1,6 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormBuilder, ReactiveFormsModule, FormsModule } from '@angular/forms';
import { Library } from 'src/app/_models/library';
import { Library } from 'src/app/_models/library/library';
import { Member } from 'src/app/_models/auth/member';
import { LibraryService } from 'src/app/_services/library.service';
import { SelectionModel } from 'src/app/typeahead/_components/typeahead.component';

View file

@ -13,7 +13,7 @@ import { ConfirmService } from 'src/app/shared/confirm.service';
import { LibrarySettingsModalComponent } from 'src/app/sidenav/_modals/library-settings-modal/library-settings-modal.component';
import { NotificationProgressEvent } from 'src/app/_models/events/notification-progress-event';
import { ScanSeriesEvent } from 'src/app/_models/events/scan-series-event';
import { Library } from 'src/app/_models/library';
import { Library } from 'src/app/_models/library/library';
import { LibraryService } from 'src/app/_services/library.service';
import { EVENTS, Message, MessageHubService } from 'src/app/_services/message-hub.service';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";

View file

@ -34,7 +34,7 @@ import { ReadingDirection } from 'src/app/_models/preferences/reading-direction'
import {WritingStyle} from "../../../_models/preferences/writing-style";
import { MangaFormat } from 'src/app/_models/manga-format';
import { LibraryService } from 'src/app/_services/library.service';
import { LibraryType } from 'src/app/_models/library';
import { LibraryType } from 'src/app/_models/library/library';
import { BookTheme } from 'src/app/_models/preferences/book-theme';
import { BookPageLayoutMode } from 'src/app/_models/readers/book-page-layout-mode';
import { PageStyle, ReaderSettingsComponent } from '../reader-settings/reader-settings.component';

View file

@ -351,24 +351,7 @@
<a ngbNavLink>{{t(tabs[TabID.WebLinks])}}</a>
<ng-template ngbNavContent>
<p>{{t('web-link-description')}}</p>
<div class="row g-0 mb-3" *ngFor="let link of WebLinks; let i = index;">
<div class="col-lg-8 col-md-12 pe-2">
<div class="mb-3">
<label for="web-link--{{i}}" class="visually-hidden">{{t('web-link-label')}}</label>
<input type="text" class="form-control" formControlName="link{{i}}" attr.id="web-link--{{i}}">
</div>
</div>
<div class="col-lg-2">
<button class="btn btn-secondary me-1" (click)="addWebLink()">
<i class="fa-solid fa-plus" aria-hidden="true"></i>
<span class="visually-hidden">{{t('add-link-alt')}}</span>
</button>
<button class="btn btn-secondary" (click)="removeWebLink(i)">
<i class="fa-solid fa-xmark" aria-hidden="true"></i>
<span class="visually-hidden">{{t('remove-link-alt')}}</span>
</button>
</div>
</div>
<app-edit-list [items]="WebLinks" [label]="t('web-link-label')" (updateItems)="updateWeblinks($event)"></app-edit-list>
</ng-template>
</li>

View file

@ -54,6 +54,7 @@ import {DefaultValuePipe} from "../../../_pipes/default-value.pipe";
import {TranslocoModule} from "@ngneat/transloco";
import {TranslocoDatePipe} from "@ngneat/transloco-locale";
import {UtcToLocalTimePipe} from "../../../_pipes/utc-to-local-time.pipe";
import {EditListComponent} from "../../../shared/edit-list/edit-list.component";
enum TabID {
General = 0,
@ -93,6 +94,7 @@ enum TabID {
TranslocoModule,
TranslocoDatePipe,
UtcToLocalTimePipe,
EditListComponent,
],
templateUrl: './edit-series-modal.component.html',
styleUrls: ['./edit-series-modal.component.scss'],
@ -100,7 +102,24 @@ enum TabID {
})
export class EditSeriesModalComponent implements OnInit {
public readonly modal = inject(NgbActiveModal);
private readonly seriesService = inject(SeriesService);
public readonly utilityService = inject(UtilityService);
private readonly fb = inject(FormBuilder);
public readonly imageService = inject(ImageService);
private readonly libraryService = inject(LibraryService);
private readonly collectionService = inject(CollectionTagService);
private readonly uploadService = inject(UploadService);
private readonly metadataService = inject(MetadataService);
private readonly cdRef = inject(ChangeDetectorRef);
protected readonly TabID = TabID;
protected readonly PersonRole = PersonRole;
protected readonly Breakpoint = Breakpoint;
@Input({required: true}) series!: Series;
seriesVolumes: any[] = [];
isLoadingVolumes = false;
/**
@ -140,18 +159,6 @@ export class EditSeriesModalComponent implements OnInit {
saveNestedComponents: EventEmitter<void> = new EventEmitter();
get Breakpoint(): typeof Breakpoint {
return Breakpoint;
}
get PersonRole() {
return PersonRole;
}
get TabID(): typeof TabID {
return TabID;
}
get WebLinks() {
return this.metadata?.webLinks.split(',') || [''];
}
@ -160,17 +167,6 @@ export class EditSeriesModalComponent implements OnInit {
return this.peopleSettings[role];
}
constructor(public modal: NgbActiveModal,
private seriesService: SeriesService,
public utilityService: UtilityService,
private fb: FormBuilder,
public imageService: ImageService,
private libraryService: LibraryService,
private collectionService: CollectionTagService,
private uploadService: UploadService,
private metadataService: MetadataService,
private readonly cdRef: ChangeDetectorRef) { }
ngOnInit(): void {
this.imageUrls.push(this.imageService.getSeriesCoverImage(this.series.id));
@ -225,10 +221,6 @@ export class EditSeriesModalComponent implements OnInit {
this.editSeriesForm.get('language')?.patchValue(this.metadata.language);
this.editSeriesForm.get('releaseYear')?.patchValue(this.metadata.releaseYear);
this.WebLinks.forEach((link, index) => {
this.editSeriesForm.addControl('link' + index, new FormControl(link, []));
});
this.cdRef.markForCheck();
this.editSeriesForm.get('name')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(val => {
@ -416,8 +408,8 @@ export class EditSeriesModalComponent implements OnInit {
if (presetField && presetField.length > 0) {
const fetch = personSettings.fetchFn as ((filter: string) => Observable<Person[]>);
return fetch('').pipe(map(people => {
const persetIds = presetField.map(p => p.id);
personSettings.savedData = people.filter(person => persetIds.includes(person.id));
const presetIds = presetField.map(p => p.id);
personSettings.savedData = people.filter(person => presetIds.includes(person.id));
this.peopleSettings[role] = personSettings;
this.updatePerson(personSettings.savedData as Person[], role);
return true;
@ -521,23 +513,14 @@ export class EditSeriesModalComponent implements OnInit {
return this.collectionService.search(filter);
}
formatChapterNumber(chapter: Chapter) {
if (chapter.number === '0') {
return '1';
}
return chapter.number;
updateWeblinks(items: Array<string>) {
this.metadata.webLinks = items.map(s => s.replaceAll(',', '%2C')).join(',');
}
save() {
const model = this.editSeriesForm.value;
const selectedIndex = this.editSeriesForm.get('coverImageIndex')?.value || 0;
this.metadata.webLinks = Object.keys(this.editSeriesForm.controls)
.filter(key => key.startsWith('link'))
.map(key => this.editSeriesForm.get(key)?.value.replace(',', '%2C'))
.filter(v => v !== null && v !== '')
.join(',');
const apis = [
this.seriesService.updateMetadata(this.metadata, this.collectionTags)
@ -566,21 +549,6 @@ export class EditSeriesModalComponent implements OnInit {
});
}
addWebLink() {
this.metadata.webLinks += ',';
this.editSeriesForm.addControl('link' + (this.WebLinks.length - 1), new FormControl('', []));
this.cdRef.markForCheck();
}
removeWebLink(index: number) {
const tokens = this.metadata.webLinks.split(',');
const tokenToRemove = tokens[index];
this.metadata.webLinks = tokens.filter(t => t != tokenToRemove).join(',');
this.editSeriesForm.removeControl('link' + index, {emitEvent: true});
this.cdRef.markForCheck();
}
updateCollections(tags: CollectionTag[]) {
this.collectionTags = tags;
this.cdRef.markForCheck();

View file

@ -23,7 +23,7 @@ import { Breakpoint, UtilityService } from 'src/app/shared/_services/utility.ser
import { Chapter } from 'src/app/_models/chapter';
import { ChapterMetadata } from 'src/app/_models/metadata/chapter-metadata';
import { Device } from 'src/app/_models/device/device';
import { LibraryType } from 'src/app/_models/library';
import { LibraryType } from 'src/app/_models/library/library';
import { MangaFile } from 'src/app/_models/manga-file';
import { MangaFormat } from 'src/app/_models/manga-format';
import { Volume } from 'src/app/_models/volume';

View file

@ -25,7 +25,7 @@ import {FilterSettings} from 'src/app/metadata-filter/filter-settings';
import {FilterUtilitiesService} from 'src/app/shared/_services/filter-utilities.service';
import {Breakpoint, UtilityService} from 'src/app/shared/_services/utility.service';
import {JumpKey} from 'src/app/_models/jumpbar/jump-key';
import {Library} from 'src/app/_models/library';
import {Library} from 'src/app/_models/library/library';
import {Pagination} from 'src/app/_models/pagination';
import {FilterEvent, FilterItem, SortField} from 'src/app/_models/metadata/series-filter';
import {ActionItem} from 'src/app/_services/action-factory.service';

View file

@ -10,7 +10,7 @@ import { UtilityService } from 'src/app/shared/_services/utility.service';
import { Chapter } from 'src/app/_models/chapter';
import { ChapterMetadata } from 'src/app/_models/metadata/chapter-metadata';
import { HourEstimateRange } from 'src/app/_models/series-detail/hour-estimate-range';
import { LibraryType } from 'src/app/_models/library';
import { LibraryType } from 'src/app/_models/library/library';
import { MangaFormat } from 'src/app/_models/manga-format';
import { AgeRating } from 'src/app/_models/metadata/age-rating';
import { Volume } from 'src/app/_models/volume';

View file

@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { UtilityService } from 'src/app/shared/_services/utility.service';
import { Chapter } from 'src/app/_models/chapter';
import { LibraryType } from 'src/app/_models/library';
import { LibraryType } from 'src/app/_models/library/library';
import { Volume } from 'src/app/_models/volume';
import {CommonModule, NgSwitch} from "@angular/common";
import {TranslocoModule} from "@ngneat/transloco";

View file

@ -14,7 +14,7 @@ import { Download } from 'src/app/shared/_models/download';
import { DownloadEvent, DownloadService } from 'src/app/shared/_services/download.service';
import {Breakpoint, UtilityService} from 'src/app/shared/_services/utility.service';
import { Chapter } from 'src/app/_models/chapter';
import { LibraryType } from 'src/app/_models/library';
import { LibraryType } from 'src/app/_models/library/library';
import { RelationKind } from 'src/app/_models/series-detail/relation-kind';
import { Volume } from 'src/app/_models/volume';
import { Action, ActionItem } from 'src/app/_services/action-factory.service';

View file

@ -4,7 +4,7 @@ import {Router, RouterLink} from '@angular/router';
import {Observable, of, ReplaySubject, Subject, switchMap} from 'rxjs';
import {debounceTime, map, shareReplay, take, tap, throttleTime} from 'rxjs/operators';
import {FilterUtilitiesService} from 'src/app/shared/_services/filter-utilities.service';
import {Library} from 'src/app/_models/library';
import {Library} from 'src/app/_models/library/library';
import {RecentlyAddedItem} from 'src/app/_models/recently-added-item';
import {SortField} from 'src/app/_models/metadata/series-filter';
import {AccountService} from 'src/app/_services/account.service';

View file

@ -14,7 +14,7 @@ import {take} from 'rxjs/operators';
import {BulkSelectionService} from '../cards/bulk-selection.service';
import {KEY_CODES, UtilityService} from '../shared/_services/utility.service';
import {SeriesAddedEvent} from '../_models/events/series-added-event';
import {Library} from '../_models/library';
import {Library} from '../_models/library/library';
import {Pagination} from '../_models/pagination';
import {Series} from '../_models/series';
import {FilterEvent} from '../_models/metadata/series-filter';

View file

@ -37,7 +37,7 @@ import {ToastrService} from 'ngx-toastr';
import {ShortcutsModalComponent} from 'src/app/reader-shared/_modals/shortcuts-modal/shortcuts-modal.component';
import {Stack} from 'src/app/shared/data-structures/stack';
import {Breakpoint, KEY_CODES, UtilityService} from 'src/app/shared/_services/utility.service';
import {LibraryType} from 'src/app/_models/library';
import {LibraryType} from 'src/app/_models/library/library';
import {MangaFormat} from 'src/app/_models/manga-format';
import {PageSplitOption} from 'src/app/_models/preferences/page-split-option';
import {layoutModes, pageSplitOptions} from 'src/app/_models/preferences/preferences';

View file

@ -1,4 +1,4 @@
import { LibraryType } from "src/app/_models/library";
import { LibraryType } from "src/app/_models/library/library";
import { MangaFormat } from "src/app/_models/manga-format";
import { FileDimension } from "./file-dimension";

View file

@ -13,7 +13,7 @@ import {
import {FormControl, FormGroup, FormsModule, ReactiveFormsModule} from '@angular/forms';
import {NgbCollapse, NgbModal, NgbRating, NgbTooltip} from '@ng-bootstrap/ng-bootstrap';
import {Breakpoint, UtilityService} from '../shared/_services/utility.service';
import {Library} from '../_models/library';
import {Library} from '../_models/library/library';
import {allSortFields, FilterEvent, FilterItem, SortField} from '../_models/metadata/series-filter';
import {ToggleService} from '../_services/toggle.service';
import {FilterSettings} from './filter-settings';

View file

@ -15,7 +15,7 @@ import {fromEvent} from 'rxjs';
import {debounceTime, distinctUntilChanged, filter, tap} from 'rxjs/operators';
import {Chapter} from 'src/app/_models/chapter';
import {CollectionTag} from 'src/app/_models/collection-tag';
import {Library} from 'src/app/_models/library';
import {Library} from 'src/app/_models/library/library';
import {MangaFile} from 'src/app/_models/manga-file';
import {PersonRole} from 'src/app/_models/metadata/person';
import {ReadingList} from 'src/app/_models/reading-list';

View file

@ -4,7 +4,7 @@ import {ToastrService} from 'ngx-toastr';
import {take} from 'rxjs/operators';
import {ConfirmService} from 'src/app/shared/confirm.service';
import {UtilityService} from 'src/app/shared/_services/utility.service';
import {LibraryType} from 'src/app/_models/library';
import {LibraryType} from 'src/app/_models/library/library';
import {MangaFormat} from 'src/app/_models/manga-format';
import {ReadingList, ReadingListItem} from 'src/app/_models/reading-list';
import {AccountService} from 'src/app/_services/account.service';

View file

@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { LibraryType } from 'src/app/_models/library';
import { LibraryType } from 'src/app/_models/library/library';
import { MangaFormat } from 'src/app/_models/manga-format';
import { ReadingListItem } from 'src/app/_models/reading-list';
import { ImageService } from 'src/app/_services/image.service';

View file

@ -14,7 +14,7 @@ import {ProviderImagePipe} from "../../../_pipes/provider-image.pipe";
import {NgbPopover, NgbRating} from "@ng-bootstrap/ng-bootstrap";
import {LoadingComponent} from "../../../shared/loading/loading.component";
import {AccountService} from "../../../_services/account.service";
import {LibraryType} from "../../../_models/library";
import {LibraryType} from "../../../_models/library/library";
import {ProviderNamePipe} from "../../../_pipes/provider-name.pipe";
import {NgxStarsModule} from "ngx-stars";
import {ThemeService} from "../../../_services/theme.service";

View file

@ -141,6 +141,7 @@
<ng-container *ngIf="renderMode === PageLayoutMode.Cards; else storylineListLayout">
<div class="card-container row g-0" #container>
<ng-container *ngFor="let item of scroll.viewPortItems; let idx = index; trackBy: trackByStoryLineIdentity">
{{item.id}}
<ng-container [ngSwitch]="item.isChapter">
<ng-container *ngSwitchCase="false" [ngTemplateOutlet]="nonChapterVolumeCard" [ngTemplateOutletContext]="{$implicit: item.volume, scroll: scroll, idx: idx, volumesLength: volumes.length}"></ng-container>
<ng-container *ngSwitchCase="true" [ngTemplateOutlet]="nonSpecialChapterCard" [ngTemplateOutletContext]="{$implicit: item.chapter, scroll: scroll, idx: idx, chaptersLength: storyChapters.length}"></ng-container>

View file

@ -44,7 +44,7 @@ import {Chapter} from 'src/app/_models/chapter';
import {Device} from 'src/app/_models/device/device';
import {ScanSeriesEvent} from 'src/app/_models/events/scan-series-event';
import {SeriesRemovedEvent} from 'src/app/_models/events/series-removed-event';
import {LibraryType} from 'src/app/_models/library';
import {LibraryType} from 'src/app/_models/library/library';
import {ReadingList} from 'src/app/_models/reading-list';
import {Series} from 'src/app/_models/series';
import {RelatedSeries} from 'src/app/_models/series-detail/related-series';

View file

@ -27,7 +27,7 @@ import {A11yClickDirective} from "../../../shared/a11y-click.directive";
import {PersonBadgeComponent} from "../../../shared/person-badge/person-badge.component";
import {NgbCollapse} from "@ng-bootstrap/ng-bootstrap";
import {SeriesInfoCardsComponent} from "../../../cards/series-info-cards/series-info-cards.component";
import {LibraryType} from "../../../_models/library";
import {LibraryType} from "../../../_models/library/library";
import {MetadataDetailComponent} from "../metadata-detail/metadata-detail.component";
import {TranslocoDirective} from "@ngneat/transloco";
import {FilterField} from "../../../_models/metadata/v2/filter-field";

View file

@ -1,7 +1,7 @@
import { HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Chapter } from 'src/app/_models/chapter';
import { LibraryType } from 'src/app/_models/library';
import { LibraryType } from 'src/app/_models/library/library';
import { MangaFormat } from 'src/app/_models/manga-format';
import { PaginatedResult } from 'src/app/_models/pagination';
import { Series } from 'src/app/_models/series';
@ -70,6 +70,7 @@ export class UtilityService {
return this.translocoService.translate('common.issue-hash-num');
}
return this.translocoService.translate('common.issue-num') + (includeSpace ? ' ' : '');
case LibraryType.Images:
case LibraryType.Manga:
return this.translocoService.translate('common.chapter-num') + (includeSpace ? ' ' : '');
}

View file

@ -0,0 +1,24 @@
<form [formGroup]="form" *transloco="let t">
@for(item of Items; let i = $index; track item) {
<div class="row g-0 mb-3">
<div class="col-lg-10 col-md-12 pe-2">
<div class="mb-3">
<label for="item--{{i}}" class="visually-hidden">{{label}}</label>
<input type="text" class="form-control" formControlName="link{{i}}" attr.id="item--{{i}}">
</div>
</div>
<div class="col-lg-2">
<button class="btn btn-secondary me-1" (click)="add()">
<i class="fa-solid fa-plus" aria-hidden="true"></i>
<span class="visually-hidden">{{t('common.add')}}</span>
</button>
<button class="btn btn-secondary" (click)="remove(i)">
<i class="fa-solid fa-xmark" aria-hidden="true"></i>
<span class="visually-hidden">{{t('common.remove')}}</span>
</button>
</div>
</div>
}
</form>

View file

@ -0,0 +1,82 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component, DestroyRef,
EventEmitter,
inject,
Input,
OnInit,
Output
} from '@angular/core';
import {CommonModule} from '@angular/common';
import {FormControl, FormGroup, ReactiveFormsModule} from "@angular/forms";
import {Select2Module} from "ng-select2-component";
import {TranslocoDirective} from "@ngneat/transloco";
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {debounceTime, distinctUntilChanged, tap} from "rxjs/operators";
@Component({
selector: 'app-edit-list',
standalone: true,
imports: [CommonModule, ReactiveFormsModule, Select2Module, TranslocoDirective],
templateUrl: './edit-list.component.html',
styleUrl: './edit-list.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class EditListComponent implements OnInit {
private readonly cdRef = inject(ChangeDetectorRef);
private readonly destroyRef = inject(DestroyRef);
@Input({required: true}) items: Array<string> = [];
@Input({required: true}) label = '';
@Output() updateItems = new EventEmitter<Array<string>>();
form: FormGroup = new FormGroup({});
private combinedItems: string = '';
get Items() {
return this.combinedItems.split(',') || [''];
}
ngOnInit() {
this.items.forEach((link, index) => {
this.form.addControl('link' + index, new FormControl(link, []));
});
this.combinedItems = this.items.join(',');
this.form.valueChanges.pipe(
debounceTime(100),
distinctUntilChanged(),
tap(data => this.emit()),
takeUntilDestroyed(this.destroyRef))
.subscribe();
this.cdRef.markForCheck();
}
add() {
this.combinedItems += ',';
this.form.addControl('link' + (this.Items.length - 1), new FormControl('', []));
this.emit();
this.cdRef.markForCheck();
}
remove(index: number) {
const tokens = this.combinedItems.split(',');
const tokenToRemove = tokens[index];
this.combinedItems = tokens.filter(t => t != tokenToRemove).join(',');
this.form.removeControl('link' + index, {emitEvent: true});
this.emit();
this.cdRef.markForCheck();
}
emit() {
this.updateItems.emit(Object.keys(this.form.controls)
.filter(key => key.startsWith('link'))
.map(key => this.form.get(key)?.value)
.filter(v => v !== null && v !== ''));
}
}

View file

@ -21,7 +21,7 @@ import {NavService} from "../../../_services/nav.service";
import {DashboardStreamListItemComponent} from "../dashboard-stream-list-item/dashboard-stream-list-item.component";
import {TranslocoDirective} from "@ngneat/transloco";
import {SidenavStreamListItemComponent} from "../sidenav-stream-list-item/sidenav-stream-list-item.component";
import {ExternalSourceService} from "../../../external-source.service";
import {ExternalSourceService} from "../../../_services/external-source.service";
import {ExternalSource} from "../../../_models/sidenav/external-source";
import {SideNavStreamType} from "../../../_models/sidenav/sidenav-stream-type.enum";
import {FormControl, FormGroup, ReactiveFormsModule} from "@angular/forms";

View file

@ -4,7 +4,7 @@ import {FormControl, FormGroup, ReactiveFormsModule, Validators} from "@angular/
import {ExternalSource} from "../../../_models/sidenav/external-source";
import {NgbCollapse} from "@ng-bootstrap/ng-bootstrap";
import {translate, TranslocoDirective} from "@ngneat/transloco";
import {ExternalSourceService} from "../../../external-source.service";
import {ExternalSourceService} from "../../../_services/external-source.service";
import {distinctUntilChanged, filter, tap} from "rxjs/operators";
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {switchMap} from "rxjs";

View file

@ -7,7 +7,7 @@ import {AccountService} from "../../../_services/account.service";
import {ToastrService} from "ngx-toastr";
import {EditExternalSourceItemComponent} from "../edit-external-source-item/edit-external-source-item.component";
import {ExternalSource} from "../../../_models/sidenav/external-source";
import {ExternalSourceService} from "../../../external-source.service";
import {ExternalSourceService} from "../../../_services/external-source.service";
import {FilterPipe} from "../../../_pipes/filter.pipe";
import {SmartFilter} from "../../../_models/metadata/v2/smart-filter";

View file

@ -13,7 +13,7 @@ import { ImportCblModalComponent } from 'src/app/reading-list/_modals/import-cbl
import { ImageService } from 'src/app/_services/image.service';
import { EVENTS, MessageHubService } from 'src/app/_services/message-hub.service';
import { Breakpoint, UtilityService } from '../../../shared/_services/utility.service';
import { Library, LibraryType } from '../../../_models/library';
import { Library, LibraryType } from '../../../_models/library/library';
import { AccountService } from '../../../_services/account.service';
import { Action, ActionFactoryService, ActionItem } from '../../../_services/action-factory.service';
import { ActionService } from '../../../_services/action.service';
@ -186,6 +186,8 @@ export class SideNavComponent implements OnInit {
case LibraryType.Comic:
case LibraryType.Manga:
return 'fa-book-open';
case LibraryType.Images:
return 'fa-images';
}
}

View file

@ -88,6 +88,48 @@
<li [ngbNavItem]="TabID.Advanced" [disabled]="isAddLibrary && setupStep < 3">
<a ngbNavLink>{{t(TabID.Advanced)}}</a>
<ng-template ngbNavContent>
<div class="row">
<div class="col-md-12 col-sm-12 pe-2 mb-2">
<div class="mb-3 mt-1">
<h6>{{t('file-type-group-label')}}</h6>
<p class="accent">
{{t('file-type-group-tooltip')}}
</p>
<div class="hstack gap-2">
<div class="form-check form-switch" *ngFor="let group of fileTypeGroups; let i = index">
<input class="form-check-input" [formControlName]="group" type="checkbox" [id]="group">
<label class="form-check-label" [for]="group">{{ group | fileTypeGroup }}</label>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 col-sm-12 pe-2 mb-2">
<div ngbAccordion>
<div ngbAccordionItem>
<h2 ngbAccordionHeader>
<button ngbAccordionButton>{{t('exclude-patterns-label')}}</button>
</h2>
<div ngbAccordionCollapse>
<div ngbAccordionBody>
<ng-template>
<span class="mb-2">{{t('exclude-patterns-tooltip')}}</span>
<a class="ms-1" href="https://wiki.kavitareader.com/en/guides/managing-your-files/scanner/excluding-files-folders" rel="noopener noreferrer" target="_blank">{{t('help')}}<i class="fa fa-external-link-alt ms-1" aria-hidden="true"></i></a>
<app-edit-list [items]="excludePatterns" [label]="t('exclude-patterns-label')" (updateItems)="updateGlobs($event)"></app-edit-list>
</ng-template>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 col-sm-12 pe-2 mb-2">
<div class="mb-3 mt-1">

View file

@ -1,6 +1,18 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, inject, Input, OnInit} from '@angular/core';
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
DestroyRef,
inject,
Input,
OnInit
} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
import {
NgbAccordionBody,
NgbAccordionButton, NgbAccordionCollapse,
NgbAccordionDirective, NgbAccordionHeader, NgbAccordionItem,
NgbActiveModal,
NgbModal,
NgbModalModule,
@ -20,7 +32,7 @@ import {
} from 'src/app/admin/_modals/directory-picker/directory-picker.component';
import {ConfirmService} from 'src/app/shared/confirm.service';
import {Breakpoint, UtilityService} from 'src/app/shared/_services/utility.service';
import {Library, LibraryType} from 'src/app/_models/library';
import {Library, LibraryType} from 'src/app/_models/library/library';
import {ImageService} from 'src/app/_services/image.service';
import {LibraryService} from 'src/app/_services/library.service';
import {UploadService} from 'src/app/_services/upload.service';
@ -30,6 +42,9 @@ import {SentenceCasePipe} from "../../../_pipes/sentence-case.pipe";
import {CoverImageChooserComponent} from "../../../cards/cover-image-chooser/cover-image-chooser.component";
import {translate, TranslocoModule} from "@ngneat/transloco";
import {DefaultDatePipe} from "../../../_pipes/default-date.pipe";
import {allFileTypeGroup, FileTypeGroup} from "../../../_models/library/file-type-group.enum";
import {FileTypeGroupPipe} from "../../../_pipes/file-type-group.pipe";
import {EditListComponent} from "../../../shared/edit-list/edit-list.component";
enum TabID {
General = 'general-tab',
@ -48,7 +63,9 @@ enum StepID {
@Component({
selector: 'app-library-settings-modal',
standalone: true,
imports: [CommonModule, NgbModalModule, NgbNavLink, NgbNavItem, NgbNavContent, ReactiveFormsModule, NgbTooltip, SentenceCasePipe, NgbNav, NgbNavOutlet, CoverImageChooserComponent, TranslocoModule, DefaultDatePipe],
imports: [CommonModule, NgbModalModule, NgbNavLink, NgbNavItem, NgbNavContent, ReactiveFormsModule, NgbTooltip,
SentenceCasePipe, NgbNav, NgbNavOutlet, CoverImageChooserComponent, TranslocoModule, DefaultDatePipe,
FileTypeGroupPipe, NgbAccordionDirective, NgbAccordionItem, NgbAccordionHeader, NgbAccordionButton, NgbAccordionCollapse, NgbAccordionBody, EditListComponent],
templateUrl: './library-settings-modal.component.html',
styleUrls: ['./library-settings-modal.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
@ -81,23 +98,24 @@ export class LibrarySettingsModalComponent implements OnInit {
isAddLibrary = false;
setupStep = StepID.General;
fileTypeGroups = allFileTypeGroup;
excludePatterns: Array<string> = [''];
protected readonly Breakpoint = Breakpoint;
protected readonly TabID = TabID;
constructor(public utilityService: UtilityService, private uploadService: UploadService, private modalService: NgbModal,
private settingService: SettingsService, public modal: NgbActiveModal, private confirmService: ConfirmService,
private libraryService: LibraryService, private toastr: ToastrService, private readonly cdRef: ChangeDetectorRef,
private imageService: ImageService) { }
ngOnInit(): void {
this.settingService.getLibraryTypes().subscribe((types) => {
this.libraryTypes = types;
this.cdRef.markForCheck();
});
if (this.library === undefined) {
this.isAddLibrary = true;
this.cdRef.markForCheck();
@ -113,7 +131,6 @@ export class LibrarySettingsModalComponent implements OnInit {
this.libraryForm.get('allowScrobbling')?.disable();
}
this.libraryForm.get('name')?.valueChanges.pipe(
debounceTime(100),
distinctUntilChanged(),
@ -132,6 +149,38 @@ export class LibrarySettingsModalComponent implements OnInit {
this.setValues();
// This needs to only apply after first render
this.libraryForm.get('type')?.valueChanges.pipe(
tap((type: LibraryType) => {
switch (type) {
case LibraryType.Manga:
this.libraryForm.get(FileTypeGroup.Archive + '')?.setValue(true);
this.libraryForm.get(FileTypeGroup.Images + '')?.setValue(true);
this.libraryForm.get(FileTypeGroup.Pdf + '')?.setValue(false);
this.libraryForm.get(FileTypeGroup.Epub + '')?.setValue(false);
break;
case LibraryType.Comic:
this.libraryForm.get(FileTypeGroup.Archive + '')?.setValue(true);
this.libraryForm.get(FileTypeGroup.Images + '')?.setValue(false);
this.libraryForm.get(FileTypeGroup.Pdf + '')?.setValue(false);
this.libraryForm.get(FileTypeGroup.Epub + '')?.setValue(false);
break;
case LibraryType.Book:
this.libraryForm.get(FileTypeGroup.Archive + '')?.setValue(false);
this.libraryForm.get(FileTypeGroup.Images + '')?.setValue(false);
this.libraryForm.get(FileTypeGroup.Pdf + '')?.setValue(true);
this.libraryForm.get(FileTypeGroup.Epub + '')?.setValue(true);
break;
case LibraryType.Images:
this.libraryForm.get(FileTypeGroup.Archive + '')?.setValue(false);
this.libraryForm.get(FileTypeGroup.Images + '')?.setValue(true);
this.libraryForm.get(FileTypeGroup.Pdf + '')?.setValue(false);
this.libraryForm.get(FileTypeGroup.Epub + '')?.setValue(false);
}
}),
takeUntilDestroyed(this.destroyRef)
).subscribe();
}
setValues() {
@ -148,8 +197,27 @@ export class LibrarySettingsModalComponent implements OnInit {
this.libraryForm.get('allowScrobbling')?.setValue(this.library.allowScrobbling);
this.selectedFolders = this.library.folders;
this.madeChanges = false;
this.cdRef.markForCheck();
for(let fileTypeGroup of allFileTypeGroup) {
this.libraryForm.addControl(fileTypeGroup + '', new FormControl(this.library.libraryFileTypes.includes(fileTypeGroup), []));
}
for(let glob of this.library.excludePatterns) {
this.libraryForm.addControl('excludeGlob-' , new FormControl(glob, []));
}
} else {
for(let fileTypeGroup of allFileTypeGroup) {
this.libraryForm.addControl(fileTypeGroup + '', new FormControl(true, []));
}
}
this.excludePatterns = this.library.excludePatterns;
if (this.excludePatterns.length === 0) {
this.excludePatterns = [''];
}
this.cdRef.markForCheck();
}
updateGlobs(items: Array<string>) {
this.excludePatterns = items;
this.cdRef.markForCheck();
}
isDisabled() {
@ -172,6 +240,13 @@ export class LibrarySettingsModalComponent implements OnInit {
async save() {
const model = this.libraryForm.value;
model.folders = this.selectedFolders;
model.fileGroupTypes = [];
for(let fileTypeGroup of allFileTypeGroup) {
if (model[fileTypeGroup]) {
model.fileGroupTypes.push(fileTypeGroup);
}
}
model.excludePatterns = this.excludePatterns;
if (this.libraryForm.errors) {
return;

View file

@ -1,4 +1,4 @@
import { Library } from "src/app/_models/library";
import { Library } from "src/app/_models/library/library";
import { Series } from "src/app/_models/series";
import { User } from "src/app/_models/user";
import { StatCount } from "./stat-count";
@ -17,4 +17,4 @@ export interface ServerStatistics {
mostActiveLibraries: Array<StatCount<Library>>;
mostReadSeries: Array<StatCount<Series>>;
recentlyRead: Array<Series>;
}
}

View file

@ -813,7 +813,19 @@
"cancel": "{{common.cancel}}",
"next": "Next",
"save": "{{common.save}}",
"required-field": "{{validation.required-field}}"
"required-field": "{{validation.required-field}}",
"file-type-group-label": "File Types",
"file-type-group-tooltip": "What types of files should Kavita scan for. For example, Archive will include all cb*, zip, rar, etc files.",
"exclude-patterns-label": "Exclude Patterns",
"exclude-patterns-tooltip": "Configure a set of patterns (Glob syntax) that Kavita will match when scanning directories and exclude from Scanner results.",
"help": "{{common.help}}"
},
"file-type-group-pipe": {
"archive": "Archive",
"epub": "Epub",
"pdf": "Pdf",
"image": "Image"
},
"reader-settings": {
@ -944,7 +956,7 @@
"description-part-2": "wiki for hints.",
"target-series": "Target Series",
"relationship": "Relationship",
"remove": "Remove",
"remove": "{{common.remove}}",
"add-relationship": "Add Relationship",
"parent": "{{relationship-pipe.parent}}"
},
@ -1414,7 +1426,7 @@
"scroll-to-top-alt": "Scroll to Top",
"server-settings": "Server Settings",
"settings": "Settings",
"help": "Help",
"help": "{{common.help}}",
"announcements": "Announcements",
"logout": "Logout",
"all-filters": "Smart Filters"
@ -1615,8 +1627,6 @@
"release-year-label": "Release Year",
"web-link-description": "Here you can add many different links to external services.",
"web-link-label": "Web Link",
"add-link-alt": "Add Link",
"remove-link-alt": "Remove Link",
"cover-image-description": "Upload and choose a new cover image. Press Save to upload and override the cover.",
"save": "{{common.save}}",
"field-locked-alt": "Field is locked",
@ -2059,6 +2069,7 @@
"add": "Add",
"apply": "Apply",
"delete": "Delete",
"remove": "Remove",
"edit": "Edit",
"help": "Help",
"submit": "Submit",