Readable Bookmarks (#1228)

* Moved bookmarks to it's own page on side nav and integrated actions.

* Implemented the ability to read bookmarks in the manga reader.

* Removed old bookmark components that aren't needed any longer.

* Removed recently added component as we use all-series instead now

* Removed bookmark tab from card detail

* Fixed scroll to top not working and being missing

* When opening the side nav on mobile with metadata filter already open, collapse the filter.

* When on mobile viewports, when clicking an item from side nav, collapse it afterwards

* Converted most of series detail to use the card detail layout, except storyline which has custom logic

* Fixed unit test
This commit is contained in:
Joseph Milazzo 2022-04-23 13:58:14 -05:00 committed by GitHub
parent 62715a9977
commit 9d6843614d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 648 additions and 634 deletions

View file

@ -1,42 +0,0 @@
<p *ngIf="series.length === 0 && !loadingBookmarks">
There are no bookmarks. Try creating <a href="https://wiki.kavitareader.com/en/guides/get-started-using-your-library/bookmarks" target="_blank">one&nbsp;<i class="fa fa-external-link-alt" aria-hidden="true"></i></a>.
</p>
<ul class="list-group">
<li *ngFor="let series of series" class="list-group-item">
<div>
<h4>
<a id="series--{{series.name}}" href="javascript:void(0);" (click)="viewBookmarks(series)">{{series.name | titlecase}}</a>
&nbsp;<span class="badge bg-secondary rounded-pill">{{getBookmarkPages(series.id)}}</span>
<div class="float-end">
<button attr.aria-labelledby="series--{{series.name}}" class="btn btn-danger me-2 btn-sm" (click)="clearBookmarks(series)" [disabled]="clearingSeries[series.id]" placement="top" ngbTooltip="Clear Bookmarks" attr.aria-label="Clear Bookmarks">
<ng-container *ngIf="clearingSeries[series.id]; else notClearing">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
<span class="visually-hidden">Loading...</span>
</ng-container>
<ng-template #notClearing>
<i class="fa fa-trash-alt" aria-hidden="true"></i>
</ng-template>
</button>
<button attr.aria-labelledby="series--{{series.name}}" class="btn btn-secondary me-2 btn-sm" (click)="downloadBookmarks(series)" [disabled]="downloadingSeries[series.id]" placement="top" ngbTooltip="Download Bookmarks" attr.aria-label="Download Bookmarks">
<ng-container *ngIf="downloadingSeries[series.id]; else notDownloading">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
<span class="visually-hidden">Downloading...</span>
</ng-container>
<ng-template #notDownloading>
<i class="fa fa-arrow-alt-circle-down" aria-hidden="true"></i>
</ng-template>
</button>
<button attr.aria-labelledby="series--{{series.name}}" class="btn btn-primary me-2 btn-sm" routerLink="/library/{{series.libraryId}}/series/{{series.id}}" placement="top" ngbTooltip="Open Series" attr.aria-label="Open Series">
<i class="fa fa-eye" title="Open Series"></i>
</button>
</div>
</h4>
</div>
</li>
<li *ngIf="loadingBookmarks" class="list-group-item">
<div class="spinner-border text-secondary" role="status">
<span class="invisible">Loading...</span>
</div>
</li>
</ul>

View file

@ -1,96 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { ToastrService } from 'ngx-toastr';
import { take, takeWhile, finalize } from 'rxjs/operators';
import { BookmarksModalComponent } from 'src/app/cards/_modals/bookmarks-modal/bookmarks-modal.component';
import { ConfirmService } from 'src/app/shared/confirm.service';
import { DownloadService } from 'src/app/shared/_services/download.service';
import { PageBookmark } from 'src/app/_models/page-bookmark';
import { Series } from 'src/app/_models/series';
import { ReaderService } from 'src/app/_services/reader.service';
import { SeriesService } from 'src/app/_services/series.service';
@Component({
selector: 'app-series-bookmarks',
templateUrl: './series-bookmarks.component.html',
styleUrls: ['./series-bookmarks.component.scss']
})
export class SeriesBookmarksComponent implements OnInit {
bookmarks: Array<PageBookmark> = [];
series: Array<Series> = [];
loadingBookmarks: boolean = false;
seriesIds: {[id: number]: number} = {};
downloadingSeries: {[id: number]: boolean} = {};
clearingSeries: {[id: number]: boolean} = {};
constructor(private readerService: ReaderService, private seriesService: SeriesService,
private modalService: NgbModal, private downloadService: DownloadService, private toastr: ToastrService,
private confirmService: ConfirmService) { }
ngOnInit(): void {
this.loadBookmarks();
}
loadBookmarks() {
this.loadingBookmarks = true;
this.readerService.getAllBookmarks().pipe(take(1)).subscribe(bookmarks => {
this.bookmarks = bookmarks;
this.seriesIds = {};
this.bookmarks.forEach(bmk => {
if (!this.seriesIds.hasOwnProperty(bmk.seriesId)) {
this.seriesIds[bmk.seriesId] = 1;
} else {
this.seriesIds[bmk.seriesId] += 1;
}
this.downloadingSeries[bmk.seriesId] = false;
this.clearingSeries[bmk.seriesId] = false;
});
const ids = Object.keys(this.seriesIds).map(k => parseInt(k, 10));
this.seriesService.getAllSeriesByIds(ids).subscribe(series => {
this.series = series;
this.loadingBookmarks = false;
});
});
}
viewBookmarks(series: Series) {
const bookmarkModalRef = this.modalService.open(BookmarksModalComponent, { scrollable: true, size: 'lg' });
bookmarkModalRef.componentInstance.series = series;
bookmarkModalRef.closed.pipe(take(1)).subscribe(() => {
this.loadBookmarks();
});
}
async clearBookmarks(series: Series) {
if (!await this.confirmService.confirm('Are you sure you want to clear all bookmarks for ' + series.name + '? This cannot be undone.')) {
return;
}
this.clearingSeries[series.id] = true;
this.readerService.clearBookmarks(series.id).subscribe(() => {
const index = this.series.indexOf(series);
if (index > -1) {
this.series.splice(index, 1);
}
this.clearingSeries[series.id] = false;
this.toastr.success(series.name + '\'s bookmarks have been removed');
});
}
getBookmarkPages(seriesId: number) {
return this.seriesIds[seriesId];
}
downloadBookmarks(series: Series) {
this.downloadingSeries[series.id] = true;
this.downloadService.downloadBookmarks(this.bookmarks.filter(bmk => bmk.seriesId === series.id)).pipe(
takeWhile(val => {
return val.state != 'DONE';
}),
finalize(() => {
this.downloadingSeries[series.id] = false;
})).subscribe(() => {/* No Operation */});
}
}

View file

@ -192,9 +192,6 @@
</ngb-panel>
</ngb-accordion>
</ng-container>
<ng-container *ngIf="tab.fragment === 'bookmarks'">
<app-series-bookmarks></app-series-bookmarks>
</ng-container>
<ng-container *ngIf="tab.fragment === 'password'">
<ng-container *ngIf="(isAdmin || hasChangePasswordRole); else noPermission">
<p>Change your Password</p>

View file

@ -56,7 +56,6 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
tabs: Array<{title: string, fragment: string}> = [
{title: 'Preferences', fragment: ''},
{title: 'Bookmarks', fragment: 'bookmarks'},
{title: 'Password', fragment: 'password'},
{title: '3rd Party Clients', fragment: 'clients'},
{title: 'Theme', fragment: 'theme'},

View file

@ -1,6 +1,5 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SeriesBookmarksComponent } from './series-bookmarks/series-bookmarks.component';
import { UserPreferencesComponent } from './user-preferences/user-preferences.component';
import { NgbAccordionModule, NgbNavModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
import { ReactiveFormsModule } from '@angular/forms';
@ -17,7 +16,6 @@ import { ColorPickerModule } from 'ngx-color-picker';
@NgModule({
declarations: [
SeriesBookmarksComponent,
UserPreferencesComponent,
ApiKeyComponent,
ThemeManagerComponent,