diff --git a/API/Controllers/CollectionController.cs b/API/Controllers/CollectionController.cs index 049413388..681a962d0 100644 --- a/API/Controllers/CollectionController.cs +++ b/API/Controllers/CollectionController.cs @@ -2,7 +2,9 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using API.Data; using API.DTOs; +using API.DTOs.CollectionTags; using API.Entities; using API.Extensions; using API.Interfaces; @@ -90,6 +92,40 @@ namespace API.Controllers return BadRequest("Something went wrong, please try again"); } + /// + /// Adds a collection tag onto multiple Series. If tag id is 0, this will create a new tag. + /// + /// + /// + [HttpPost("update-for-series")] + public async Task AddToMultipleSeries(CollectionTagBulkAddDto dto) + { + var tag = await _unitOfWork.CollectionTagRepository.GetFullTagAsync(dto.CollectionTagId); + if (tag == null) + { + tag = DbFactory.CollectionTag(0, dto.CollectionTagTitle, String.Empty, false); + _unitOfWork.CollectionTagRepository.Add(tag); + } + + + var seriesMetadatas = await _unitOfWork.SeriesRepository.GetSeriesMetadataForIdsAsync(dto.SeriesIds); + foreach (var metadata in seriesMetadatas) + { + if (!metadata.CollectionTags.Any(t => t.Title.Equals(tag.Title, StringComparison.InvariantCulture))) + { + metadata.CollectionTags.Add(tag); + _unitOfWork.SeriesMetadataRepository.Update(metadata); + } + } + + if (!_unitOfWork.HasChanges()) return Ok(); + if (await _unitOfWork.CommitAsync()) + { + return Ok(); + } + return BadRequest("There was an issue updating series with collection tag"); + } + /// /// For a given tag, update the summary if summary has changed and remove a set of series from the tag. /// diff --git a/API/Controllers/OPDSController.cs b/API/Controllers/OPDSController.cs index c9e527e2c..22103eb2b 100644 --- a/API/Controllers/OPDSController.cs +++ b/API/Controllers/OPDSController.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using System.Xml.Serialization; using API.Comparators; using API.DTOs; +using API.DTOs.CollectionTags; using API.DTOs.Filtering; using API.DTOs.OPDS; using API.Entities; diff --git a/API/DTOs/CollectionTags/CollectionTagBulkAddDto.cs b/API/DTOs/CollectionTags/CollectionTagBulkAddDto.cs new file mode 100644 index 000000000..ac28e81cb --- /dev/null +++ b/API/DTOs/CollectionTags/CollectionTagBulkAddDto.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace API.DTOs.CollectionTags +{ + public class CollectionTagBulkAddDto + { + /// + /// Collection Tag Id + /// + /// Can be 0 which then will use Title to create a tag + public int CollectionTagId { get; init; } + public string CollectionTagTitle { get; init; } + /// + /// Series Ids to add onto Collection Tag + /// + public IEnumerable SeriesIds { get; init; } + } +} diff --git a/API/DTOs/CollectionTagDto.cs b/API/DTOs/CollectionTags/CollectionTagDto.cs similarity index 87% rename from API/DTOs/CollectionTagDto.cs rename to API/DTOs/CollectionTags/CollectionTagDto.cs index cb9870610..8612f19e0 100644 --- a/API/DTOs/CollectionTagDto.cs +++ b/API/DTOs/CollectionTags/CollectionTagDto.cs @@ -1,4 +1,4 @@ -namespace API.DTOs +namespace API.DTOs.CollectionTags { public class CollectionTagDto { diff --git a/API/DTOs/UpdateSeriesForTagDto.cs b/API/DTOs/CollectionTags/UpdateSeriesForTagDto.cs similarity index 58% rename from API/DTOs/UpdateSeriesForTagDto.cs rename to API/DTOs/CollectionTags/UpdateSeriesForTagDto.cs index 743981165..1a844ee18 100644 --- a/API/DTOs/UpdateSeriesForTagDto.cs +++ b/API/DTOs/CollectionTags/UpdateSeriesForTagDto.cs @@ -1,10 +1,10 @@ using System.Collections.Generic; -namespace API.DTOs +namespace API.DTOs.CollectionTags { public class UpdateSeriesForTagDto { public CollectionTagDto Tag { get; init; } - public ICollection SeriesIdsToRemove { get; init; } + public IEnumerable SeriesIdsToRemove { get; init; } } -} \ No newline at end of file +} diff --git a/API/DTOs/SeriesMetadataDto.cs b/API/DTOs/SeriesMetadataDto.cs index 47d5cbee2..69dcae2d9 100644 --- a/API/DTOs/SeriesMetadataDto.cs +++ b/API/DTOs/SeriesMetadataDto.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using API.DTOs.CollectionTags; using API.Entities; namespace API.DTOs diff --git a/API/DTOs/UpdateSeriesMetadataDto.cs b/API/DTOs/UpdateSeriesMetadataDto.cs index a9c852632..dd43167c9 100644 --- a/API/DTOs/UpdateSeriesMetadataDto.cs +++ b/API/DTOs/UpdateSeriesMetadataDto.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using API.DTOs.CollectionTags; namespace API.DTOs { diff --git a/API/Data/Repositories/CollectionTagRepository.cs b/API/Data/Repositories/CollectionTagRepository.cs index 2766e6ba7..182fa86b8 100644 --- a/API/Data/Repositories/CollectionTagRepository.cs +++ b/API/Data/Repositories/CollectionTagRepository.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Threading.Tasks; using API.DTOs; +using API.DTOs.CollectionTags; using API.Entities; using API.Interfaces.Repositories; using AutoMapper; @@ -21,6 +22,11 @@ namespace API.Data.Repositories _mapper = mapper; } + public void Add(CollectionTag tag) + { + _context.CollectionTag.Add(tag); + } + public void Remove(CollectionTag tag) { _context.CollectionTag.Remove(tag); diff --git a/API/Data/Repositories/SeriesMetadataRepository.cs b/API/Data/Repositories/SeriesMetadataRepository.cs new file mode 100644 index 000000000..32ab0f4e2 --- /dev/null +++ b/API/Data/Repositories/SeriesMetadataRepository.cs @@ -0,0 +1,20 @@ +using API.Entities; +using API.Interfaces.Repositories; + +namespace API.Data.Repositories +{ + public class SeriesMetadataRepository : ISeriesMetadataRepository + { + private readonly DataContext _context; + + public SeriesMetadataRepository(DataContext context) + { + _context = context; + } + + public void Update(SeriesMetadata seriesMetadata) + { + _context.SeriesMetadata.Update(seriesMetadata); + } + } +} diff --git a/API/Data/Repositories/SeriesRepository.cs b/API/Data/Repositories/SeriesRepository.cs index 67cd83276..281c2e325 100644 --- a/API/Data/Repositories/SeriesRepository.cs +++ b/API/Data/Repositories/SeriesRepository.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using API.Data.Scanner; using API.DTOs; +using API.DTOs.CollectionTags; using API.DTOs.Filtering; using API.Entities; using API.Extensions; @@ -485,5 +486,13 @@ namespace API.Data.Repositories TotalChunks = totalChunks }; } + + public async Task> GetSeriesMetadataForIdsAsync(IEnumerable seriesIds) + { + return await _context.SeriesMetadata + .Where(sm => seriesIds.Contains(sm.SeriesId)) + .Include(sm => sm.CollectionTags) + .ToListAsync(); + } } } diff --git a/API/Data/UnitOfWork.cs b/API/Data/UnitOfWork.cs index 64f9c4fe0..a1f797188 100644 --- a/API/Data/UnitOfWork.cs +++ b/API/Data/UnitOfWork.cs @@ -34,6 +34,7 @@ namespace API.Data public IFileRepository FileRepository => new FileRepository(_context); public IChapterRepository ChapterRepository => new ChapterRepository(_context, _mapper); public IReadingListRepository ReadingListRepository => new ReadingListRepository(_context, _mapper); + public ISeriesMetadataRepository SeriesMetadataRepository => new SeriesMetadataRepository(_context); /// /// Commits changes to the DB. Completes the open transaction. diff --git a/API/Entities/ServerSetting.cs b/API/Entities/ServerSetting.cs index b09ae71e0..6c4b5f21d 100644 --- a/API/Entities/ServerSetting.cs +++ b/API/Entities/ServerSetting.cs @@ -8,6 +8,9 @@ namespace API.Entities { [Key] public ServerSettingKey Key { get; set; } + /// + /// The value of the Setting. Converter knows how to convert to the correct type + /// public string Value { get; set; } /// diff --git a/API/Helpers/AutoMapperProfiles.cs b/API/Helpers/AutoMapperProfiles.cs index ff1a30e34..74bd8d57c 100644 --- a/API/Helpers/AutoMapperProfiles.cs +++ b/API/Helpers/AutoMapperProfiles.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using API.DTOs; +using API.DTOs.CollectionTags; using API.DTOs.Reader; using API.DTOs.ReadingLists; using API.DTOs.Settings; diff --git a/API/Interfaces/IUnitOfWork.cs b/API/Interfaces/IUnitOfWork.cs index 06b47bbe8..733008192 100644 --- a/API/Interfaces/IUnitOfWork.cs +++ b/API/Interfaces/IUnitOfWork.cs @@ -15,6 +15,7 @@ namespace API.Interfaces IFileRepository FileRepository { get; } IChapterRepository ChapterRepository { get; } IReadingListRepository ReadingListRepository { get; } + ISeriesMetadataRepository SeriesMetadataRepository { get; } bool Commit(); Task CommitAsync(); bool HasChanges(); diff --git a/API/Interfaces/Repositories/ICollectionTagRepository.cs b/API/Interfaces/Repositories/ICollectionTagRepository.cs index 03a552bd9..18c9f490b 100644 --- a/API/Interfaces/Repositories/ICollectionTagRepository.cs +++ b/API/Interfaces/Repositories/ICollectionTagRepository.cs @@ -1,12 +1,14 @@ using System.Collections.Generic; using System.Threading.Tasks; using API.DTOs; +using API.DTOs.CollectionTags; using API.Entities; namespace API.Interfaces.Repositories { public interface ICollectionTagRepository { + void Add(CollectionTag tag); void Remove(CollectionTag tag); Task> GetAllTagDtosAsync(); Task> SearchTagDtosAsync(string searchQuery); diff --git a/API/Interfaces/Repositories/ISeriesMetadataRepository.cs b/API/Interfaces/Repositories/ISeriesMetadataRepository.cs new file mode 100644 index 000000000..00dd234ee --- /dev/null +++ b/API/Interfaces/Repositories/ISeriesMetadataRepository.cs @@ -0,0 +1,9 @@ +using API.Entities; + +namespace API.Interfaces.Repositories +{ + public interface ISeriesMetadataRepository + { + void Update(SeriesMetadata seriesMetadata); + } +} diff --git a/API/Interfaces/Repositories/ISeriesRepository.cs b/API/Interfaces/Repositories/ISeriesRepository.cs index 0b3ed8eeb..b080a904f 100644 --- a/API/Interfaces/Repositories/ISeriesRepository.cs +++ b/API/Interfaces/Repositories/ISeriesRepository.cs @@ -54,5 +54,6 @@ namespace API.Interfaces.Repositories Task> GetFullSeriesForLibraryIdAsync(int libraryId, UserParams userParams); Task GetFullSeriesForSeriesIdAsync(int seriesId); Task GetChunkInfo(int libraryId = 0); + Task> GetSeriesMetadataForIdsAsync(IEnumerable seriesIds); } } diff --git a/API/Parser/Parser.cs b/API/Parser/Parser.cs index 9bd2aab82..e082f06e0 100644 --- a/API/Parser/Parser.cs +++ b/API/Parser/Parser.cs @@ -26,7 +26,7 @@ namespace API.Parser public static readonly Regex FontSrcUrlRegex = new Regex(@"(src:url\(.{1})" + "([^\"']*)" + @"(.{1}\))", MatchOptions, RegexTimeout); - public static readonly Regex CssImportUrlRegex = new Regex("(@import\\s[\"|'])(?[\\w\\d/\\._-]+)([\"|'];?)", + public static readonly Regex CssImportUrlRegex = new Regex("@import\\s([\"|']|url\\([\"|'])(?[^'\"]+)[\"|']\\)?;", MatchOptions, RegexTimeout); private static readonly string XmlRegexExtensions = @"\.xml"; diff --git a/UI/Web/src/app/_services/action-factory.service.ts b/UI/Web/src/app/_services/action-factory.service.ts index 450b577bc..0df929e8f 100644 --- a/UI/Web/src/app/_services/action-factory.service.ts +++ b/UI/Web/src/app/_services/action-factory.service.ts @@ -19,7 +19,8 @@ export enum Action { Download = 7, Bookmarks = 8, IncognitoRead = 9, - AddToReadingList = 10 + AddToReadingList = 10, + AddToCollection = 11 } export interface ActionItem { @@ -90,6 +91,13 @@ export class ActionFactoryService { requiresAdmin: true }); + this.seriesActions.push({ + action: Action.AddToCollection, + title: 'Add to Collection', + callback: this.dummyCallback, + requiresAdmin: true + }); + this.seriesActions.push({ action: Action.Edit, title: 'Edit', @@ -209,7 +217,7 @@ export class ActionFactoryService { title: 'Add to Reading List', callback: this.dummyCallback, requiresAdmin: false - }, + } ]; this.volumeActions = [ diff --git a/UI/Web/src/app/_services/action.service.ts b/UI/Web/src/app/_services/action.service.ts index 3d654108f..ad79a5b32 100644 --- a/UI/Web/src/app/_services/action.service.ts +++ b/UI/Web/src/app/_services/action.service.ts @@ -4,6 +4,7 @@ import { ToastrService } from 'ngx-toastr'; import { Subject } from 'rxjs'; import { take } from 'rxjs/operators'; import { BookmarksModalComponent } from '../cards/_modals/bookmarks-modal/bookmarks-modal.component'; +import { BulkAddToCollectionComponent } from '../cards/_modals/bulk-add-to-collection/bulk-add-to-collection.component'; import { AddToListModalComponent, ADD_FLOW } from '../reading-list/_modals/add-to-list-modal/add-to-list-modal.component'; import { EditReadingListModalComponent } from '../reading-list/_modals/edit-reading-list-modal/edit-reading-list-modal.component'; import { ConfirmService } from '../shared/confirm.service'; @@ -34,6 +35,7 @@ export class ActionService implements OnDestroy { private readonly onDestroy = new Subject(); private bookmarkModalRef: NgbModalRef | null = null; private readingListModalRef: NgbModalRef | null = null; + private collectionModalRef: NgbModalRef | null = null; constructor(private libraryService: LibraryService, private seriesService: SeriesService, private readerService: ReaderService, private toastr: ToastrService, private modalService: NgbModal, @@ -358,6 +360,32 @@ export class ActionService implements OnDestroy { }); } + /** + * Adds a set of series to a collection tag + * @param series + * @param callback + * @returns + */ + addMultipleSeriesToCollectionTag(series: Array, callback?: VoidActionCallback) { + if (this.collectionModalRef != null) { return; } + this.collectionModalRef = this.modalService.open(BulkAddToCollectionComponent, { scrollable: true, size: 'md' }); + this.collectionModalRef.componentInstance.seriesIds = series.map(v => v.id); + this.collectionModalRef.componentInstance.title = 'New Collection'; + + this.collectionModalRef.closed.pipe(take(1)).subscribe(() => { + this.collectionModalRef = null; + if (callback) { + callback(); + } + }); + this.collectionModalRef.dismissed.pipe(take(1)).subscribe(() => { + this.collectionModalRef = null; + if (callback) { + callback(); + } + }); + } + addSeriesToReadingList(series: Series, callback?: SeriesActionCallback) { if (this.readingListModalRef != null) { return; } this.readingListModalRef = this.modalService.open(AddToListModalComponent, { scrollable: true, size: 'md' }); diff --git a/UI/Web/src/app/_services/collection-tag.service.ts b/UI/Web/src/app/_services/collection-tag.service.ts index 87d275f43..dd8571b6a 100644 --- a/UI/Web/src/app/_services/collection-tag.service.ts +++ b/UI/Web/src/app/_services/collection-tag.service.ts @@ -35,4 +35,8 @@ export class CollectionTagService { updateSeriesForTag(tag: CollectionTag, seriesIdsToRemove: Array) { return this.httpClient.post(this.baseUrl + 'collection/update-series', {tag, seriesIdsToRemove}, {responseType: 'text' as 'json'}); } + + addByMultiple(tagId: number, seriesIds: Array, tagTitle: string = '') { + return this.httpClient.post(this.baseUrl + 'collection/update-for-series', {collectionTagId: tagId, collectionTagTitle: tagTitle, seriesIds}, {responseType: 'text' as 'json'}); + } } diff --git a/UI/Web/src/app/cards/_modals/bulk-add-to-collection/bulk-add-to-collection.component.html b/UI/Web/src/app/cards/_modals/bulk-add-to-collection/bulk-add-to-collection.component.html new file mode 100644 index 000000000..2e7dcecb2 --- /dev/null +++ b/UI/Web/src/app/cards/_modals/bulk-add-to-collection/bulk-add-to-collection.component.html @@ -0,0 +1,46 @@ + + +
+ + +
+ + diff --git a/UI/Web/src/app/cards/_modals/bulk-add-to-collection/bulk-add-to-collection.component.scss b/UI/Web/src/app/cards/_modals/bulk-add-to-collection/bulk-add-to-collection.component.scss new file mode 100644 index 000000000..91847160a --- /dev/null +++ b/UI/Web/src/app/cards/_modals/bulk-add-to-collection/bulk-add-to-collection.component.scss @@ -0,0 +1,7 @@ +.clickable { + cursor: pointer; +} + +.clickable:hover, .clickable:focus { + background-color: lightgreen; +} \ No newline at end of file diff --git a/UI/Web/src/app/cards/_modals/bulk-add-to-collection/bulk-add-to-collection.component.ts b/UI/Web/src/app/cards/_modals/bulk-add-to-collection/bulk-add-to-collection.component.ts new file mode 100644 index 000000000..13641550b --- /dev/null +++ b/UI/Web/src/app/cards/_modals/bulk-add-to-collection/bulk-add-to-collection.component.ts @@ -0,0 +1,79 @@ +import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; +import { FormGroup, FormControl } from '@angular/forms'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { ToastrService } from 'ngx-toastr'; +import { CollectionTag } from 'src/app/_models/collection-tag'; +import { ReadingList } from 'src/app/_models/reading-list'; +import { CollectionTagService } from 'src/app/_services/collection-tag.service'; + +@Component({ + selector: 'app-bulk-add-to-collection', + templateUrl: './bulk-add-to-collection.component.html', + styleUrls: ['./bulk-add-to-collection.component.scss'] +}) +export class BulkAddToCollectionComponent implements OnInit { + + @Input() title!: string; + /** + * Series Ids to add to Collection Tag + */ + @Input() seriesIds: Array = []; + + /** + * All existing collections sorted by recent use date + */ + lists: Array = []; + loading: boolean = false; + listForm: FormGroup = new FormGroup({}); + + @ViewChild('title') inputElem!: ElementRef; + + + constructor(private modal: NgbActiveModal, private collectionService: CollectionTagService, private toastr: ToastrService) { } + + ngOnInit(): void { + + this.listForm.addControl('title', new FormControl(this.title, [])); + this.listForm.addControl('filterQuery', new FormControl('', [])); + + this.loading = true; + this.collectionService.allTags().subscribe(tags => { + this.lists = tags; + this.loading = false; + }); + } + + ngAfterViewInit() { + // Shift focus to input + if (this.inputElem) { + this.inputElem.nativeElement.select(); + } + } + + close() { + this.modal.close(); + } + + create() { + const tagName = this.listForm.value.title; + this.collectionService.addByMultiple(0, this.seriesIds, tagName).subscribe(() => { + this.toastr.success('Series added to ' + tagName + ' collection'); + this.modal.close(); + }); + } + + addToCollection(tag: CollectionTag) { + if (this.seriesIds.length === 0) return; + + this.collectionService.addByMultiple(tag.id, this.seriesIds, '').subscribe(() => { + this.toastr.success('Series added to ' + tag.title + ' collection'); + this.modal.close(); + }); + + } + + filterList = (listItem: ReadingList) => { + return listItem.title.toLowerCase().indexOf((this.listForm.value.filterQuery || '').toLowerCase()) >= 0; + } + +} diff --git a/UI/Web/src/app/cards/bulk-selection.service.ts b/UI/Web/src/app/cards/bulk-selection.service.ts index 47072c014..1cc205725 100644 --- a/UI/Web/src/app/cards/bulk-selection.service.ts +++ b/UI/Web/src/app/cards/bulk-selection.service.ts @@ -127,7 +127,7 @@ export class BulkSelectionService { getActions(callback: (action: Action, data: any) => void) { // checks if series is present. If so, returns only series actions // else returns volume/chapter items - const allowedActions = [Action.AddToReadingList, Action.MarkAsRead, Action.MarkAsUnread]; + const allowedActions = [Action.AddToReadingList, Action.MarkAsRead, Action.MarkAsUnread, Action.AddToCollection]; if (Object.keys(this.selectedCards).filter(item => item === 'series').length > 0) { return this.actionFactory.getSeriesActions(callback).filter(item => allowedActions.includes(item.action)); } diff --git a/UI/Web/src/app/cards/cards.module.ts b/UI/Web/src/app/cards/cards.module.ts index 7c7db5c54..dc1a23134 100644 --- a/UI/Web/src/app/cards/cards.module.ts +++ b/UI/Web/src/app/cards/cards.module.ts @@ -16,10 +16,11 @@ import { CardItemComponent } from './card-item/card-item.component'; import { SharedModule } from '../shared/shared.module'; import { RouterModule } from '@angular/router'; import { TypeaheadModule } from '../typeahead/typeahead.module'; -import { BrowserModule } from '@angular/platform-browser'; import { CardDetailLayoutComponent } from './card-detail-layout/card-detail-layout.component'; import { CardDetailsModalComponent } from './_modals/card-details-modal/card-details-modal.component'; import { BulkOperationsComponent } from './bulk-operations/bulk-operations.component'; +import { BulkAddToCollectionComponent } from './_modals/bulk-add-to-collection/bulk-add-to-collection.component'; +import { PipeModule } from '../pipe/pipe.module'; @@ -36,11 +37,11 @@ import { BulkOperationsComponent } from './bulk-operations/bulk-operations.compo CardActionablesComponent, CardDetailLayoutComponent, CardDetailsModalComponent, - BulkOperationsComponent + BulkOperationsComponent, + BulkAddToCollectionComponent ], imports: [ CommonModule, - //BrowserModule, RouterModule, ReactiveFormsModule, FormsModule, // EditCollectionsModal @@ -58,6 +59,7 @@ import { BulkOperationsComponent } from './bulk-operations/bulk-operations.compo NgbDropdownModule, NgbProgressbarModule, NgxFileDropModule, // Cover Chooser + PipeModule // filter for BulkAddToCollectionComponent ], exports: [ CardItemComponent, diff --git a/UI/Web/src/app/collections/collection-detail/collection-detail.component.ts b/UI/Web/src/app/collections/collection-detail/collection-detail.component.ts index 086df24a3..34ca7b659 100644 --- a/UI/Web/src/app/collections/collection-detail/collection-detail.component.ts +++ b/UI/Web/src/app/collections/collection-detail/collection-detail.component.ts @@ -55,6 +55,11 @@ export class CollectionDetailComponent implements OnInit, OnDestroy { this.bulkSelectionService.deselectAll(); }); break; + case Action.AddToCollection: + this.actionService.addMultipleSeriesToCollectionTag(selectedSeries, () => { + this.bulkSelectionService.deselectAll(); + }); + break; case Action.MarkAsRead: this.actionService.markMultipleSeriesAsRead(selectedSeries, () => { this.loadPage(); diff --git a/UI/Web/src/app/library-detail/library-detail.component.ts b/UI/Web/src/app/library-detail/library-detail.component.ts index 57c85d789..678a52d20 100644 --- a/UI/Web/src/app/library-detail/library-detail.component.ts +++ b/UI/Web/src/app/library-detail/library-detail.component.ts @@ -46,6 +46,11 @@ export class LibraryDetailComponent implements OnInit, OnDestroy { this.bulkSelectionService.deselectAll(); }); break; + case Action.AddToCollection: + this.actionService.addMultipleSeriesToCollectionTag(selectedSeries, () => { + this.bulkSelectionService.deselectAll(); + }); + break; case Action.MarkAsRead: this.actionService.markMultipleSeriesAsRead(selectedSeries, () => { this.loadPage(); 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/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';