@@ -122,7 +125,7 @@
-
{{bookTitle}} (Incognito Mode)
+
{{bookTitle}} (Incognito Mode)
-
{{title}} (Incognito Mode)
+
{{title}} (Incognito Mode:)
{{subtitle}}
diff --git a/UI/Web/src/app/manga-reader/manga-reader.component.ts b/UI/Web/src/app/manga-reader/manga-reader.component.ts
index ec9c0ba90..e41d9cc30 100644
--- a/UI/Web/src/app/manga-reader/manga-reader.component.ts
+++ b/UI/Web/src/app/manga-reader/manga-reader.component.ts
@@ -1113,4 +1113,15 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
}
}
+
+ /**
+ * Turns off Incognito mode. This can only happen once if the user clicks the icon. This will modify URL state
+ */
+ turnOffIncognito() {
+ this.incognitoMode = false;
+ const newRoute = this.readerService.getNextChapterUrl(this.router.url, this.chapterId, this.incognitoMode, this.readingListMode, this.readingListId);
+ window.history.replaceState({}, '', newRoute);
+ this.toastr.info('Incognito mode is off. Progress will now start being tracked.');
+ this.readerService.saveProgress(this.seriesId, this.volumeId, this.chapterId, this.pageNum).pipe(take(1)).subscribe(() => {/* No operation */});
+ }
}
diff --git a/UI/Web/src/app/nav-header/nav-header.component.html b/UI/Web/src/app/nav-header/nav-header.component.html
index 87bc92ec5..b96bd73f6 100644
--- a/UI/Web/src/app/nav-header/nav-header.component.html
+++ b/UI/Web/src/app/nav-header/nav-header.component.html
@@ -23,6 +23,7 @@
(selected)='clickSearchResult($event)'
(inputChanged)='onChangeSearch($event)'
[isLoading]="isLoading"
+ [customFilter]="customFilter"
[debounceTime]="debounceTime"
[itemTemplate]="itemTemplate"
[notFoundTemplate]="notFoundTemplate">
@@ -35,7 +36,7 @@
-
= 0; else localizedName" [innerHTML]="item.name">
+
= 0; else localizedName" [innerHTML]="item.name">
diff --git a/UI/Web/src/app/nav-header/nav-header.component.ts b/UI/Web/src/app/nav-header/nav-header.component.ts
index d9005bc10..b2f7896e1 100644
--- a/UI/Web/src/app/nav-header/nav-header.component.ts
+++ b/UI/Web/src/app/nav-header/nav-header.component.ts
@@ -3,6 +3,7 @@ import { Component, HostListener, Inject, OnDestroy, OnInit, ViewChild } from '@
import { Router } from '@angular/router';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
+import { isTemplateSpan } from 'typescript';
import { ScrollService } from '../scroll.service';
import { SearchResult } from '../_models/search-result';
import { AccountService } from '../_services/account.service';
@@ -24,6 +25,16 @@ export class NavHeaderComponent implements OnInit, OnDestroy {
imageStyles = {width: '24px', 'margin-top': '5px'};
searchResults: SearchResult[] = [];
searchTerm = '';
+ customFilter: (items: SearchResult[], query: string) => SearchResult[] = (items: SearchResult[], query: string) => {
+ const normalizedQuery = query.trim().toLowerCase();
+ const matches = items.filter(item => {
+ const normalizedSeriesName = item.name.toLowerCase().trim();
+ const normalizedOriginalName = item.originalName.toLowerCase().trim();
+ const normalizedLocalizedName = item.localizedName.toLowerCase().trim();
+ return normalizedSeriesName.indexOf(normalizedQuery) >= 0 || normalizedOriginalName.indexOf(normalizedQuery) >= 0 || normalizedLocalizedName.indexOf(normalizedQuery) >= 0;
+ });
+ return matches;
+ };
backToTopNeeded = false;
diff --git a/UI/Web/src/app/reading-list/_modals/add-to-list-modal/add-to-list-modal.component.ts b/UI/Web/src/app/reading-list/_modals/add-to-list-modal/add-to-list-modal.component.ts
index a33d69332..3d88fcd1b 100644
--- a/UI/Web/src/app/reading-list/_modals/add-to-list-modal/add-to-list-modal.component.ts
+++ b/UI/Web/src/app/reading-list/_modals/add-to-list-modal/add-to-list-modal.component.ts
@@ -1,6 +1,5 @@
-import { noUndefined } from '@angular/compiler/src/util';
import { AfterViewInit, Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core';
-import { FormControl, FormGroup, Validators } from '@angular/forms';
+import { FormControl, FormGroup } from '@angular/forms';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { ToastrService } from 'ngx-toastr';
import { ReadingList } from 'src/app/_models/reading-list';
diff --git a/UI/Web/src/app/recently-added/recently-added.component.html b/UI/Web/src/app/recently-added/recently-added.component.html
index b9bfbec74..93952f73e 100644
--- a/UI/Web/src/app/recently-added/recently-added.component.html
+++ b/UI/Web/src/app/recently-added/recently-added.component.html
@@ -1,14 +1,13 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/UI/Web/src/app/recently-added/recently-added.component.ts b/UI/Web/src/app/recently-added/recently-added.component.ts
index 4615f610a..97640118d 100644
--- a/UI/Web/src/app/recently-added/recently-added.component.ts
+++ b/UI/Web/src/app/recently-added/recently-added.component.ts
@@ -1,11 +1,18 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, HostListener, OnDestroy, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { ActivatedRoute, Router } from '@angular/router';
-import { take } from 'rxjs/operators';
+import { Subject } from 'rxjs';
+import { debounceTime, take, takeUntil, takeWhile } from 'rxjs/operators';
+import { BulkSelectionService } from '../cards/bulk-selection.service';
import { UpdateFilterEvent } from '../cards/card-detail-layout/card-detail-layout.component';
+import { KEY_CODES } from '../shared/_services/utility.service';
+import { SeriesAddedEvent } from '../_models/events/series-added-event';
import { Pagination } from '../_models/pagination';
import { Series } from '../_models/series';
import { FilterItem, mangaFormatFilters, SeriesFilter } from '../_models/series-filter';
+import { Action, ActionFactoryService } from '../_services/action-factory.service';
+import { ActionService } from '../_services/action.service';
+import { MessageHubService } from '../_services/message-hub.service';
import { SeriesService } from '../_services/series.service';
/**
@@ -16,10 +23,10 @@ import { SeriesService } from '../_services/series.service';
templateUrl: './recently-added.component.html',
styleUrls: ['./recently-added.component.scss']
})
-export class RecentlyAddedComponent implements OnInit {
+export class RecentlyAddedComponent implements OnInit, OnDestroy {
isLoading: boolean = true;
- recentlyAdded: Series[] = [];
+ series: Series[] = [];
pagination!: Pagination;
libraryId!: number;
@@ -28,7 +35,10 @@ export class RecentlyAddedComponent implements OnInit {
mangaFormat: null
};
- constructor(private router: Router, private route: ActivatedRoute, private seriesService: SeriesService, private titleService: Title) {
+ onDestroy: Subject
= new Subject();
+
+ constructor(private router: Router, private route: ActivatedRoute, private seriesService: SeriesService, private titleService: Title,
+ private actionService: ActionService, public bulkSelectionService: BulkSelectionService, private hubService: MessageHubService) {
this.router.routeReuseStrategy.shouldReuseRoute = () => false;
this.titleService.setTitle('Kavita - Recently Added');
if (this.pagination === undefined || this.pagination === null) {
@@ -37,7 +47,30 @@ export class RecentlyAddedComponent implements OnInit {
this.loadPage();
}
- ngOnInit() {}
+ @HostListener('document:keydown.shift', ['$event'])
+ handleKeypress(event: KeyboardEvent) {
+ if (event.key === KEY_CODES.SHIFT) {
+ this.bulkSelectionService.isShiftDown = true;
+ }
+ }
+
+ @HostListener('document:keyup.shift', ['$event'])
+ handleKeyUp(event: KeyboardEvent) {
+ if (event.key === KEY_CODES.SHIFT) {
+ this.bulkSelectionService.isShiftDown = false;
+ }
+ }
+
+ ngOnInit() {
+ this.hubService.seriesAdded.pipe(takeWhile(event => event.libraryId === this.libraryId), debounceTime(6000), takeUntil(this.onDestroy)).subscribe((event: SeriesAddedEvent) => {
+ this.loadPage();
+ });
+ }
+
+ ngOnDestroy() {
+ this.onDestroy.next();
+ this.onDestroy.complete();
+ }
seriesClicked(series: Series) {
this.router.navigate(['library', this.libraryId, 'series', series.id]);
@@ -65,7 +98,7 @@ export class RecentlyAddedComponent implements OnInit {
}
this.isLoading = true;
this.seriesService.getRecentlyAdded(this.libraryId, this.pagination?.currentPage, this.pagination?.itemsPerPage, this.filter).pipe(take(1)).subscribe(series => {
- this.recentlyAdded = series.result;
+ this.series = series.result;
this.pagination = series.pagination;
this.isLoading = false;
window.scrollTo(0, 0);
@@ -76,4 +109,41 @@ export class RecentlyAddedComponent implements OnInit {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get('page');
}
+
+ bulkActionCallback = (action: Action, data: any) => {
+ const selectedSeriesIndexies = this.bulkSelectionService.getSelectedCardsForSource('series');
+ const selectedSeries = this.series.filter((series, index: number) => selectedSeriesIndexies.includes(index + ''));
+
+ switch (action) {
+ case Action.AddToReadingList:
+ this.actionService.addMultipleSeriesToReadingList(selectedSeries, () => {
+ this.bulkSelectionService.deselectAll();
+ });
+ break;
+ case Action.AddToCollection:
+ this.actionService.addMultipleSeriesToCollectionTag(selectedSeries, () => {
+ this.bulkSelectionService.deselectAll();
+ });
+ break;
+ case Action.MarkAsRead:
+ this.actionService.markMultipleSeriesAsRead(selectedSeries, () => {
+ this.loadPage();
+ this.bulkSelectionService.deselectAll();
+ });
+
+ break;
+ case Action.MarkAsUnread:
+ this.actionService.markMultipleSeriesAsUnread(selectedSeries, () => {
+ this.loadPage();
+ this.bulkSelectionService.deselectAll();
+ });
+ break;
+ case Action.Delete:
+ this.actionService.deleteMultipleSeries(selectedSeries, () => {
+ this.loadPage();
+ this.bulkSelectionService.deselectAll();
+ });
+ break;
+ }
+ }
}
diff --git a/UI/Web/src/app/typeahead/typeahead.component.scss b/UI/Web/src/app/typeahead/typeahead.component.scss
index 2672736ad..d798e2c5b 100644
--- a/UI/Web/src/app/typeahead/typeahead.component.scss
+++ b/UI/Web/src/app/typeahead/typeahead.component.scss
@@ -10,7 +10,7 @@ input {
.typeahead-input {
border: 1px solid #ccc;
- padding: 4px 6px;
+ padding: 0px 6px;
display: inline-block;
width: 100%;
overflow: hidden;
diff --git a/UI/Web/src/app/typeahead/typeahead.component.ts b/UI/Web/src/app/typeahead/typeahead.component.ts
index 3ebf81280..99b721f18 100644
--- a/UI/Web/src/app/typeahead/typeahead.component.ts
+++ b/UI/Web/src/app/typeahead/typeahead.component.ts
@@ -1,7 +1,7 @@
import { Component, ContentChild, ElementRef, EventEmitter, HostListener, Input, OnDestroy, OnInit, Output, Renderer2, RendererStyleFlags2, TemplateRef, ViewChild } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
-import { Observable, Observer, of, Subject } from 'rxjs';
-import { debounceTime, distinctUntilChanged, filter, last, map, shareReplay, switchMap, take, takeLast, takeUntil, tap, withLatestFrom } from 'rxjs/operators';
+import { Observable, of, Subject } from 'rxjs';
+import { debounceTime, filter, map, shareReplay, switchMap, take, takeUntil, tap } from 'rxjs/operators';
import { KEY_CODES } from '../shared/_services/utility.service';
import { TypeaheadSettings } from './typeahead-settings';
diff --git a/UI/Web/src/app/user-login/user-login.component.html b/UI/Web/src/app/user-login/user-login.component.html
index 9caa02e73..226b42057 100644
--- a/UI/Web/src/app/user-login/user-login.component.html
+++ b/UI/Web/src/app/user-login/user-login.component.html
@@ -1,6 +1,6 @@