mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Fixes before Release (#3172)
This commit is contained in:
parent
844d7c7e4b
commit
d6ee97816f
@ -478,9 +478,6 @@ public class SeriesService : ISeriesService
|
|||||||
|
|
||||||
foreach (var chapter in chapters)
|
foreach (var chapter in chapters)
|
||||||
{
|
{
|
||||||
// if (!string.IsNullOrEmpty(chapter.TitleName)) chapter.Title = chapter.TitleName;
|
|
||||||
// else chapter.Title = await FormatChapterTitle(userId, chapter, libraryType);
|
|
||||||
|
|
||||||
chapter.Title = await FormatChapterTitle(userId, chapter, libraryType);
|
chapter.Title = await FormatChapterTitle(userId, chapter, libraryType);
|
||||||
|
|
||||||
if (!chapter.IsSpecial) continue;
|
if (!chapter.IsSpecial) continue;
|
||||||
@ -536,7 +533,12 @@ public class SeriesService : ISeriesService
|
|||||||
{
|
{
|
||||||
var firstChapter = volume.Chapters.First();
|
var firstChapter = volume.Chapters.First();
|
||||||
// On Books, skip volumes that are specials, since these will be shown
|
// On Books, skip volumes that are specials, since these will be shown
|
||||||
if (firstChapter.IsSpecial) return false;
|
// if (firstChapter.IsSpecial)
|
||||||
|
// {
|
||||||
|
// // Some books can be SP marker and also position of 0, this will trick Kavita into rendering it as part of a non-special volume
|
||||||
|
// // We need to rename the entity so that it renders out correctly
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
if (string.IsNullOrEmpty(firstChapter.TitleName))
|
if (string.IsNullOrEmpty(firstChapter.TitleName))
|
||||||
{
|
{
|
||||||
if (firstChapter.Range.Equals(Parser.LooseLeafVolume)) return false;
|
if (firstChapter.Range.Equals(Parser.LooseLeafVolume)) return false;
|
||||||
@ -550,7 +552,7 @@ public class SeriesService : ISeriesService
|
|||||||
volume.Name = firstChapter.TitleName;
|
volume.Name = firstChapter.TitleName;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return !firstChapter.IsSpecial;
|
||||||
}
|
}
|
||||||
|
|
||||||
volume.Name = $"{volumeLabel.Trim()} {volume.Name}".Trim();
|
volume.Name = $"{volumeLabel.Trim()} {volume.Name}".Trim();
|
||||||
|
@ -13,7 +13,7 @@ public class BookParser(IDirectoryService directoryService, IBookService bookSer
|
|||||||
info.ComicInfo = comicInfo;
|
info.ComicInfo = comicInfo;
|
||||||
|
|
||||||
// We need a special piece of code to override the Series IF there is a special marker in the filename for epub files
|
// We need a special piece of code to override the Series IF there is a special marker in the filename for epub files
|
||||||
if (info.IsSpecial && info.Volumes == "0" && info.ComicInfo.Series != info.Series)
|
if (info.IsSpecial && info.Volumes is "0" or "0.0" && info.ComicInfo.Series != info.Series)
|
||||||
{
|
{
|
||||||
info.Series = info.ComicInfo.Series;
|
info.Series = info.ComicInfo.Series;
|
||||||
}
|
}
|
||||||
|
@ -36,5 +36,4 @@ Run `npm run start`
|
|||||||
- all components must be standalone
|
- all components must be standalone
|
||||||
|
|
||||||
# Update latest angular
|
# Update latest angular
|
||||||
`ng update @angular/core @angular/cli @typescript-es
|
`ng update @angular/core @angular/cli @typescript-eslint/parser @angular/localize @angular/compiler-cli @angular-devkit/build-angular @angular/cdk`
|
||||||
lint/parser @angular/localize @angular/compiler-cli @angular/cli @angular-devkit/build-angular @angular/cdk`
|
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
<ng-container *transloco="let t; read: 'actionable'">
|
||||||
|
<div class="modal-container">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title">{{t('title')}}</h4>
|
||||||
|
<button type="button" class="btn-close" [attr.aria-label]="t('close')" (click)="modal.close()"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body scrollable-modal">
|
||||||
|
@if (currentLevel.length > 0) {
|
||||||
|
<button class="btn btn-secondary w-100 mb-3 text-start" (click)="handleBack()">
|
||||||
|
← {{t('back-to', {action: t(currentLevel[currentLevel.length - 1])})}}
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="d-grid gap-2">
|
||||||
|
@for (action of currentItems; track action.title) {
|
||||||
|
@if (willRenderAction(action)) {
|
||||||
|
<button class="btn btn-outline-primary text-start d-flex justify-content-between align-items-center w-100"
|
||||||
|
(click)="handleItemClick(action)">
|
||||||
|
{{t(action.title)}}
|
||||||
|
@if (action.children.length > 0 || action.dynamicList) {
|
||||||
|
<span class="ms-1">→</span>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
@ -0,0 +1,98 @@
|
|||||||
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Component, DestroyRef,
|
||||||
|
EventEmitter,
|
||||||
|
inject,
|
||||||
|
Input,
|
||||||
|
OnInit,
|
||||||
|
Output
|
||||||
|
} from '@angular/core';
|
||||||
|
import {NgClass} from "@angular/common";
|
||||||
|
import {TranslocoDirective} from "@jsverse/transloco";
|
||||||
|
import {Breakpoint, UtilityService} from "../../shared/_services/utility.service";
|
||||||
|
import {NgbActiveModal} from "@ng-bootstrap/ng-bootstrap";
|
||||||
|
import {Action, ActionItem} from "../../_services/action-factory.service";
|
||||||
|
import {AccountService} from "../../_services/account.service";
|
||||||
|
import {tap} from "rxjs";
|
||||||
|
import {User} from "../../_models/user";
|
||||||
|
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-actionable-modal',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
NgClass,
|
||||||
|
TranslocoDirective
|
||||||
|
],
|
||||||
|
templateUrl: './actionable-modal.component.html',
|
||||||
|
styleUrl: './actionable-modal.component.scss',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class ActionableModalComponent implements OnInit {
|
||||||
|
|
||||||
|
protected readonly utilityService = inject(UtilityService);
|
||||||
|
protected readonly modal = inject(NgbActiveModal);
|
||||||
|
protected readonly accountService = inject(AccountService);
|
||||||
|
protected readonly cdRef = inject(ChangeDetectorRef);
|
||||||
|
protected readonly destroyRef = inject(DestroyRef);
|
||||||
|
protected readonly Breakpoint = Breakpoint;
|
||||||
|
|
||||||
|
@Input() actions: ActionItem<any>[] = [];
|
||||||
|
@Input() willRenderAction!: (action: ActionItem<any>) => boolean;
|
||||||
|
@Input() shouldRenderSubMenu!: (action: ActionItem<any>, dynamicList: null | Array<any>) => boolean;
|
||||||
|
@Output() actionPerformed = new EventEmitter<ActionItem<any>>();
|
||||||
|
|
||||||
|
currentLevel: string[] = [];
|
||||||
|
currentItems: ActionItem<any>[] = [];
|
||||||
|
user!: User | undefined;
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.currentItems = this.actions;
|
||||||
|
this.accountService.currentUser$.pipe(tap(user => {
|
||||||
|
this.user = user;
|
||||||
|
this.cdRef.markForCheck();
|
||||||
|
}), takeUntilDestroyed(this.destroyRef)).subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleItemClick(item: ActionItem<any>) {
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
this.currentLevel.push(item.title);
|
||||||
|
this.currentItems = item.children;
|
||||||
|
} else if (item.dynamicList) {
|
||||||
|
item.dynamicList.subscribe(dynamicItems => {
|
||||||
|
this.currentLevel.push(item.title);
|
||||||
|
this.currentItems = dynamicItems.map(di => ({
|
||||||
|
...item,
|
||||||
|
title: di.title,
|
||||||
|
_extra: di
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.actionPerformed.emit(item);
|
||||||
|
this.modal.close(item);
|
||||||
|
}
|
||||||
|
this.cdRef.markForCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleBack() {
|
||||||
|
if (this.currentLevel.length > 0) {
|
||||||
|
this.currentLevel.pop();
|
||||||
|
|
||||||
|
let items = this.actions;
|
||||||
|
for (let level of this.currentLevel) {
|
||||||
|
items = items.find(item => item.title === level)?.children || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentItems = items;
|
||||||
|
this.cdRef.markForCheck();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// willRenderAction(action: ActionItem<any>) {
|
||||||
|
// if (this.user === undefined) return false;
|
||||||
|
//
|
||||||
|
// return this.accountService.canInvokeAction(this.user, action.action);
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
@ -1,40 +1,45 @@
|
|||||||
<ng-container *transloco="let t; read: 'actionable'">
|
<ng-container *transloco="let t; read: 'actionable'">
|
||||||
@if (actions.length > 0) {
|
@if (actions.length > 0) {
|
||||||
<div ngbDropdown container="body" class="d-inline-block">
|
@if ((utilityService.activeBreakpoint$ | async)! <= Breakpoint.Tablet) {
|
||||||
<button [disabled]="disabled" class="btn {{btnClass}}" id="actions-{{labelBy}}" ngbDropdownToggle
|
<button [disabled]="disabled" class="btn {{btnClass}}" id="actions-{{labelBy}}"
|
||||||
(click)="preventEvent($event)"><i class="fa {{iconClass}}" aria-hidden="true"></i></button>
|
(click)="openMobileActionableMenu($event)"><i class="fa {{iconClass}}" aria-hidden="true"></i></button>
|
||||||
<div ngbDropdownMenu attr.aria-labelledby="actions-{{labelBy}}">
|
} @else {
|
||||||
<ng-container *ngTemplateOutlet="submenu; context: { list: actions }"></ng-container>
|
<div ngbDropdown container="body" class="d-inline-block">
|
||||||
|
<button [disabled]="disabled" class="btn {{btnClass}}" id="actions-{{labelBy}}" ngbDropdownToggle
|
||||||
|
(click)="preventEvent($event)"><i class="fa {{iconClass}}" aria-hidden="true"></i></button>
|
||||||
|
<div ngbDropdownMenu attr.aria-labelledby="actions-{{labelBy}}">
|
||||||
|
<ng-container *ngTemplateOutlet="submenu; context: { list: actions }"></ng-container>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<ng-template #submenu let-list="list">
|
||||||
<ng-template #submenu let-list="list">
|
@for(action of list; track action.id) {
|
||||||
@for(action of list; track action.id) {
|
<!-- Non Submenu items -->
|
||||||
<!-- Non Submenu items -->
|
@if (action.children === undefined || action?.children?.length === 0 || action.dynamicList !== undefined) {
|
||||||
@if (action.children === undefined || action?.children?.length === 0 || action.dynamicList !== undefined) {
|
@if (action.dynamicList !== undefined && (action.dynamicList | async | dynamicList); as dList) {
|
||||||
@if (action.dynamicList !== undefined && (action.dynamicList | async | dynamicList); as dList) {
|
@for(dynamicItem of dList; track dynamicItem.title) {
|
||||||
@for(dynamicItem of dList; track dynamicItem.title) {
|
<button ngbDropdownItem (click)="performDynamicClick($event, action, dynamicItem)">{{dynamicItem.title}}</button>
|
||||||
<button ngbDropdownItem (click)="performDynamicClick($event, action, dynamicItem)">{{dynamicItem.title}}</button>
|
|
||||||
}
|
|
||||||
} @else if (willRenderAction(action)) {
|
|
||||||
<button ngbDropdownItem (click)="performAction($event, action)" (mouseover)="closeAllSubmenus()">{{t(action.title)}}</button>
|
|
||||||
}
|
|
||||||
} @else {
|
|
||||||
@if (shouldRenderSubMenu(action, action.children?.[0].dynamicList | async)) {
|
|
||||||
<!-- Submenu items -->
|
|
||||||
<div ngbDropdown #subMenuHover="ngbDropdown" placement="right left"
|
|
||||||
(click)="preventEvent($event); openSubmenu(action.title, subMenuHover)"
|
|
||||||
(mouseover)="preventEvent($event); openSubmenu(action.title, subMenuHover)"
|
|
||||||
(mouseleave)="preventEvent($event)">
|
|
||||||
@if (willRenderAction(action)) {
|
|
||||||
<button id="actions-{{action.title}}" class="submenu-toggle" ngbDropdownToggle>{{t(action.title)}} <i class="fa-solid fa-angle-right submenu-icon"></i></button>
|
|
||||||
}
|
}
|
||||||
<div ngbDropdownMenu attr.aria-labelledby="actions-{{action.title}}">
|
} @else if (willRenderAction(action)) {
|
||||||
<ng-container *ngTemplateOutlet="submenu; context: { list: action.children }"></ng-container>
|
<button ngbDropdownItem (click)="performAction($event, action)" (mouseover)="closeAllSubmenus()">{{t(action.title)}}</button>
|
||||||
|
}
|
||||||
|
} @else {
|
||||||
|
@if (shouldRenderSubMenu(action, action.children?.[0].dynamicList | async)) {
|
||||||
|
<!-- Submenu items -->
|
||||||
|
<div ngbDropdown #subMenuHover="ngbDropdown" placement="right left"
|
||||||
|
(click)="preventEvent($event); openSubmenu(action.title, subMenuHover)"
|
||||||
|
(mouseover)="preventEvent($event); openSubmenu(action.title, subMenuHover)"
|
||||||
|
(mouseleave)="preventEvent($event)">
|
||||||
|
@if (willRenderAction(action)) {
|
||||||
|
<button id="actions-{{action.title}}" class="submenu-toggle" ngbDropdownToggle>{{t(action.title)}} <i class="fa-solid fa-angle-right submenu-icon"></i></button>
|
||||||
|
}
|
||||||
|
<div ngbDropdownMenu attr.aria-labelledby="actions-{{action.title}}">
|
||||||
|
<ng-container *ngTemplateOutlet="submenu; context: { list: action.children }"></ng-container>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
</ng-template>
|
||||||
</ng-template>
|
}
|
||||||
}
|
}
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@ -8,13 +8,16 @@ import {
|
|||||||
OnInit,
|
OnInit,
|
||||||
Output
|
Output
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import {NgbDropdown, NgbDropdownItem, NgbDropdownMenu, NgbDropdownToggle} from '@ng-bootstrap/ng-bootstrap';
|
import {NgbDropdown, NgbDropdownItem, NgbDropdownMenu, NgbDropdownToggle, NgbModal} from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { AccountService } from 'src/app/_services/account.service';
|
import { AccountService } from 'src/app/_services/account.service';
|
||||||
import { Action, ActionItem } from 'src/app/_services/action-factory.service';
|
import { Action, ActionItem } from 'src/app/_services/action-factory.service';
|
||||||
import {AsyncPipe, NgTemplateOutlet} from "@angular/common";
|
import {AsyncPipe, NgTemplateOutlet} from "@angular/common";
|
||||||
import {TranslocoDirective} from "@jsverse/transloco";
|
import {TranslocoDirective} from "@jsverse/transloco";
|
||||||
import {DynamicListPipe} from "./_pipes/dynamic-list.pipe";
|
import {DynamicListPipe} from "./_pipes/dynamic-list.pipe";
|
||||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||||
|
import {Breakpoint, UtilityService} from "../../shared/_services/utility.service";
|
||||||
|
import {NavLinkModalComponent} from "../../nav/_components/nav-link-modal/nav-link-modal.component";
|
||||||
|
import {ActionableModalComponent} from "../actionable-modal/actionable-modal.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-card-actionables',
|
selector: 'app-card-actionables',
|
||||||
@ -29,6 +32,10 @@ export class CardActionablesComponent implements OnInit {
|
|||||||
private readonly cdRef = inject(ChangeDetectorRef);
|
private readonly cdRef = inject(ChangeDetectorRef);
|
||||||
private readonly accountService = inject(AccountService);
|
private readonly accountService = inject(AccountService);
|
||||||
private readonly destroyRef = inject(DestroyRef);
|
private readonly destroyRef = inject(DestroyRef);
|
||||||
|
protected readonly utilityService = inject(UtilityService);
|
||||||
|
protected readonly modalService = inject(NgbModal);
|
||||||
|
|
||||||
|
protected readonly Breakpoint = Breakpoint;
|
||||||
|
|
||||||
@Input() iconClass = 'fa-ellipsis-v';
|
@Input() iconClass = 'fa-ellipsis-v';
|
||||||
@Input() btnClass = '';
|
@Input() btnClass = '';
|
||||||
@ -110,4 +117,16 @@ export class CardActionablesComponent implements OnInit {
|
|||||||
action._extra = dynamicItem;
|
action._extra = dynamicItem;
|
||||||
this.performAction(event, action);
|
this.performAction(event, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openMobileActionableMenu(event: any) {
|
||||||
|
this.preventEvent(event);
|
||||||
|
|
||||||
|
const ref = this.modalService.open(ActionableModalComponent, {fullscreen: 'sm'});
|
||||||
|
ref.componentInstance.actions = this.actions;
|
||||||
|
ref.componentInstance.willRenderAction = this.willRenderAction.bind(this);
|
||||||
|
ref.componentInstance.shouldRenderSubMenu = this.shouldRenderSubMenu.bind(this);
|
||||||
|
ref.componentInstance.actionPerformed.subscribe((action: ActionItem<any>) => {
|
||||||
|
this.performAction(event, action);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ import {
|
|||||||
TrackByFunction,
|
TrackByFunction,
|
||||||
ViewChild
|
ViewChild
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import {NavigationEnd, NavigationStart, Router} from '@angular/router';
|
import {NavigationStart, Router} from '@angular/router';
|
||||||
import {VirtualScrollerComponent, VirtualScrollerModule} from '@iharbeck/ngx-virtual-scroller';
|
import {VirtualScrollerComponent, VirtualScrollerModule} from '@iharbeck/ngx-virtual-scroller';
|
||||||
import {FilterSettings} from 'src/app/metadata-filter/filter-settings';
|
import {FilterSettings} from 'src/app/metadata-filter/filter-settings';
|
||||||
import {FilterUtilitiesService} from 'src/app/shared/_services/filter-utilities.service';
|
import {FilterUtilitiesService} from 'src/app/shared/_services/filter-utilities.service';
|
||||||
|
@ -812,7 +812,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
&& (this.readerService.imageUrlToChapterId(img.src) == chapterId || this.readerService.imageUrlToChapterId(img.src) === -1)
|
&& (this.readerService.imageUrlToChapterId(img.src) == chapterId || this.readerService.imageUrlToChapterId(img.src) === -1)
|
||||||
);
|
);
|
||||||
|
|
||||||
//console.log('Requesting page ', pageNum, ' found page: ', img, ' and app is requesting new image? ', forceNew);
|
console.log('Requesting page ', pageNum, ' found page: ', img, ' and app is requesting new image? ', forceNew);
|
||||||
if (!img || forceNew) {
|
if (!img || forceNew) {
|
||||||
img = new Image();
|
img = new Image();
|
||||||
img.src = this.getPageUrl(pageNum, chapterId);
|
img.src = this.getPageUrl(pageNum, chapterId);
|
||||||
@ -1416,7 +1416,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
let numOffset = this.pageNum + i;
|
let numOffset = this.pageNum + i;
|
||||||
|
|
||||||
if (numOffset > this.maxPages - 1) {
|
if (numOffset > this.maxPages - 1) {
|
||||||
continue;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const index = (numOffset % this.cachedImages.length + this.cachedImages.length) % this.cachedImages.length;
|
const index = (numOffset % this.cachedImages.length + this.cachedImages.length) % this.cachedImages.length;
|
||||||
@ -1532,14 +1532,15 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
if (direction === PAGING_DIRECTION.BACKWARDS) {
|
if (direction === PAGING_DIRECTION.BACKWARDS) {
|
||||||
if (this.continuousChapterInfos[ChapterInfoPosition.Previous] === undefined) return;
|
if (this.continuousChapterInfos[ChapterInfoPosition.Previous] === undefined) return;
|
||||||
const n = this.continuousChapterInfos[ChapterInfoPosition.Previous]!.pages;
|
const n = this.continuousChapterInfos[ChapterInfoPosition.Previous]!.pages;
|
||||||
pages = Array.from({length: n + 1}, (v, k) => n - k);
|
// Ensure we only load up to 5 pages backward
|
||||||
|
pages = Array.from({ length: Math.min(n + 1, 5) }, (v, k) => n - k);
|
||||||
} else {
|
} else {
|
||||||
pages = [0, 1, 2, 3, 4];
|
pages = [0, 1, 2, 3, 4];
|
||||||
}
|
}
|
||||||
|
|
||||||
let images = [];
|
const images = [];
|
||||||
pages.forEach((_, i: number) => {
|
pages.forEach((_, i: number) => {
|
||||||
let img = new Image();
|
const img = new Image();
|
||||||
img.src = this.getPageUrl(i, chapterId);
|
img.src = this.getPageUrl(i, chapterId);
|
||||||
images.push(img)
|
images.push(img)
|
||||||
});
|
});
|
||||||
|
@ -4,13 +4,13 @@
|
|||||||
<app-step-tracker [steps]="steps" [currentStep]="currentStepIndex"></app-step-tracker>
|
<app-step-tracker [steps]="steps" [currentStep]="currentStepIndex"></app-step-tracker>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- This is going to need to have a fixed height with a scrollbar-->
|
|
||||||
<div>
|
<div>
|
||||||
@switch (currentStepIndex) {
|
@switch (currentStepIndex) {
|
||||||
@case (Step.Import) {
|
@case (Step.Import) {
|
||||||
<div class="row g-0">
|
<div class="row g-0">
|
||||||
<p>{{t('import-description')}}</p>
|
<p>{{t('import-description')}}</p>
|
||||||
<p>{{t('cbl-repo') | safeHtml}}</p>
|
<p [innerHTML]="t('cbl-repo') | safeHtml"></p>
|
||||||
|
|
||||||
<form [formGroup]="uploadForm" enctype="multipart/form-data">
|
<form [formGroup]="uploadForm" enctype="multipart/form-data">
|
||||||
<file-upload formControlName="files"></file-upload>
|
<file-upload formControlName="files"></file-upload>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1462,7 +1462,6 @@
|
|||||||
"theme": "Theme",
|
"theme": "Theme",
|
||||||
"customize": "Customize",
|
"customize": "Customize",
|
||||||
"cbl-import": "CBL Reading List",
|
"cbl-import": "CBL Reading List",
|
||||||
"cbl-repo": "You can find many reading lists in the community <a href='https://github.com/DieselTech/CBL-ReadingLists' target='_blank' rel='noopener noreferrer'>repo</a>.",
|
|
||||||
"mal-stack-import": "MAL Stack"
|
"mal-stack-import": "MAL Stack"
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1668,7 +1667,8 @@
|
|||||||
"validate-cbl-step": "Validate CBL",
|
"validate-cbl-step": "Validate CBL",
|
||||||
"dry-run-step": "Dry Run",
|
"dry-run-step": "Dry Run",
|
||||||
"final-import-step": "Final Step",
|
"final-import-step": "Final Step",
|
||||||
"comicvine-parsing-label": "Use Comic Vine Series matching"
|
"comicvine-parsing-label": "Use Comic Vine Series matching",
|
||||||
|
"cbl-repo": "You can find many reading lists in the community <a href='https://github.com/DieselTech/CBL-ReadingLists' target='_blank' rel='noopener noreferrer'>repo</a>."
|
||||||
},
|
},
|
||||||
|
|
||||||
"pdf-reader": {
|
"pdf-reader": {
|
||||||
@ -2420,7 +2420,10 @@
|
|||||||
"promote": "Promote",
|
"promote": "Promote",
|
||||||
"promote-tooltip": "Make the item visible to all users",
|
"promote-tooltip": "Make the item visible to all users",
|
||||||
"new-collection": "New Collection",
|
"new-collection": "New Collection",
|
||||||
"multiple-selections": "Multiple Selections"
|
"multiple-selections": "Multiple Selections",
|
||||||
|
"back-to": "Back to {{action}}",
|
||||||
|
"title": "Actions"
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"preferences": {
|
"preferences": {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"openapi": "3.0.1",
|
"openapi": "3.0.1",
|
||||||
"info": {
|
"info": {
|
||||||
"title": "Kavita",
|
"title": "Kavita",
|
||||||
"description": "Kavita provides a set of APIs that are authenticated by JWT. JWT token can be copied from local storage. Assume all fields of a payload are required. Built against v0.8.2.10",
|
"description": "Kavita provides a set of APIs that are authenticated by JWT. JWT token can be copied from local storage. Assume all fields of a payload are required. Built against v0.8.2.11",
|
||||||
"license": {
|
"license": {
|
||||||
"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"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user