Bookmark Refactor (#893)

* Fixed a bug which didn't take sort direction when not changing sort field

* Added foundation for Bookmark refactor

* Code broken, need to take a break. Issue is Getting bookmark image needs authentication but UI doesn't send.

* Implemented the ability to send bookmarked files to the web. Implemented ability to clear bookmarks on disk on a re-occuring basis.

* Updated the bookmark design to have it's own card that is self contained. View bookmarks modal has been updated to better lay out the cards.

* Refactored download bookmark codes to select files from bookmark directory directly rather than open underlying files.

* Wrote the basic logic to kick start the bookmark migration.

Added Installed Version into the DB to allow us to know more accurately when to run migrations

* Implemented the ability to change the bookmarks directory

* Updated all references to BookmarkDirectory to use setting from the DB.

Updated Server Settings page to use 2 col for some rows.

* Refactored some code to DirectoryService (hasWriteAccess) and fixed up some unit tests from a previous PR.

* Treat folders that start with ._ as blacklisted.

* Implemented Reset User preferences. Some extra code to prep for the migration.

* Implemented a migration for existing bookmarks to using new filesystem based bookmarks
This commit is contained in:
Joseph Milazzo 2022-01-05 09:56:49 -08:00 committed by GitHub
parent 04ffd1ef6f
commit a1a6333f09
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 2006 additions and 103 deletions

View file

@ -5,15 +5,16 @@
</button>
</div>
<div class="modal-body">
<ul class="list-unstyled">
<li class="list-group-item" *ngIf="bookmarks.length > 0">
There are {{bookmarks.length}} pages bookmarked over {{uniqueChapters}} files.
</li>
<li class="list-group-item" *ngIf="bookmarks.length === 0">
No bookmarks yet
</li>
</ul>
<p *ngIf="bookmarks.length > 0; else noBookmarks">
There are {{bookmarks.length}} pages bookmarked over {{uniqueChapters}} files.
</p>
<ng-template #noBookmarks>No bookmarks yet</ng-template>
<div class="row no-gutters">
<div *ngFor="let bookmark of bookmarks; let idx = index">
<app-bookmark [bookmark]="bookmark" (bookmarkRemoved)="removeBookmark(bookmark, idx)" class="col-auto"></app-bookmark>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" (click)="clearBookmarks()" [disabled]="(isDownloading || isClearing) || bookmarks.length === 0">

View file

@ -51,6 +51,10 @@ export class BookmarksModalComponent implements OnInit {
this.modal.close();
}
removeBookmark(bookmark: PageBookmark, index: number) {
this.bookmarks.splice(index, 1);
}
downloadBookmarks() {
this.isDownloading = true;
this.downloadService.downloadBookmarks(this.bookmarks).pipe(

View file

@ -0,0 +1,28 @@
<div class="card" *ngIf="bookmark != undefined">
<img class="img-top lazyload" [src]="imageService.placeholderImage" [attr.data-src]="imageService.getBookmarkedImage(bookmark.chapterId, bookmark.page)"
(error)="imageService.updateErroredImage($event)" aria-hidden="true" height="230px" width="170px">
<div class="card-body" *ngIf="bookmark.page >= 0">
<div class="header-row">
<span class="card-title" tabindex="0">
Page {{bookmark.page + 1}}
</span>
<span class="card-actions float-right" *ngIf="series != undefined">
<button attr.aria-labelledby="series--{{series.name}}" class="btn btn-danger btn-sm" (click)="removeBookmark()"
[disabled]="isClearing" placement="top" ngbTooltip="Remove Bookmark" attr.aria-label="Remove Bookmark">
<ng-container *ngIf="isClearing; else notClearing">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
<span class="sr-only">Loading...</span>
</ng-container>
<ng-template #notClearing>
<i class="fa fa-trash-alt" aria-hidden="true"></i>
</ng-template>
</button>
</span>
</div>
<div>
<a *ngIf="series != undefined" class="title-overflow library" href="/library/{{series.libraryId}}/series/{{series.id}}"
placement="top" id="bookmark_card_{{series.name}}_{{bookmark.id}}" [ngbTooltip]="series.name | titlecase">{{series.name | titlecase}}</a>
</div>
</div>
</div>

View file

@ -0,0 +1,25 @@
.card-body {
padding: 5px;
}
.card {
margin-left: 5px;
margin-right: 5px;
}
.header-row {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.title-overflow {
font-size: 13px;
width: 130px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
display: block;
margin-top: 2px;
margin-bottom: 0px;
}

View file

@ -0,0 +1,43 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Series } from 'src/app/_models/series';
import { ImageService } from 'src/app/_services/image.service';
import { ReaderService } from 'src/app/_services/reader.service';
import { SeriesService } from 'src/app/_services/series.service';
import { PageBookmark } from '../../_models/page-bookmark';
@Component({
selector: 'app-bookmark',
templateUrl: './bookmark.component.html',
styleUrls: ['./bookmark.component.scss']
})
export class BookmarkComponent implements OnInit {
@Input() bookmark: PageBookmark | undefined;
@Output() bookmarkRemoved: EventEmitter<PageBookmark> = new EventEmitter<PageBookmark>();
series: Series | undefined;
isClearing: boolean = false;
isDownloading: boolean = false;
constructor(public imageService: ImageService, private seriesService: SeriesService, private readerService: ReaderService) { }
ngOnInit(): void {
if (this.bookmark) {
this.seriesService.getSeries(this.bookmark.seriesId).subscribe(series => {
this.series = series;
});
}
}
handleClick(event: any) {
}
removeBookmark() {
if (this.bookmark === undefined) return;
this.readerService.unbookmark(this.bookmark.seriesId, this.bookmark.volumeId, this.bookmark.chapterId, this.bookmark.page).subscribe(res => {
this.bookmarkRemoved.emit(this.bookmark);
this.bookmark = undefined;
});
}
}

View file

@ -20,13 +20,13 @@
<div class="not-read-badge" *ngIf="read === 0 && total > 0"></div>
<div class="bulk-mode {{bulkSelectionService.hasSelections() ? 'always-show' : ''}}" (click)="handleSelection($event)" *ngIf="allowSelection">
<input type="checkbox" attr.aria-labelledby="{{title}}_{{entity.id}}" [ngModel]="selected" [ngModelOptions]="{standalone: true}">
<input type="checkbox" attr.aria-labelledby="{{title}}_{{entity?.id}}" [ngModel]="selected" [ngModelOptions]="{standalone: true}">
</div>
</div>
<div class="card-body" *ngIf="title.length > 0 || actions.length > 0">
<div>
<span class="card-title" placement="top" id="{{title}}_{{entity.id}}" [ngbTooltip]="tooltipTitle" (click)="handleClick()" tabindex="0">
<span class="card-title" placement="top" id="{{title}}_{{entity?.id}}" [ngbTooltip]="tooltipTitle" (click)="handleClick()" tabindex="0">
<span *ngIf="isPromoted()">
<i class="fa fa-angle-double-up" aria-hidden="true"></i>
<span class="sr-only">(promoted)</span>

View file

@ -8,6 +8,7 @@ import { UtilityService } from 'src/app/shared/_services/utility.service';
import { Chapter } from 'src/app/_models/chapter';
import { CollectionTag } from 'src/app/_models/collection-tag';
import { MangaFormat } from 'src/app/_models/manga-format';
import { PageBookmark } from 'src/app/_models/page-bookmark';
import { Series } from 'src/app/_models/series';
import { Volume } from 'src/app/_models/volume';
import { Action, ActionItem } from 'src/app/_services/action-factory.service';
@ -49,7 +50,7 @@ export class CardItemComponent implements OnInit, OnDestroy {
/**
* This is the entity we are representing. It will be returned if an action is executed.
*/
@Input() entity!: Series | Volume | Chapter | CollectionTag;
@Input() entity!: Series | Volume | Chapter | CollectionTag | PageBookmark;
/**
* If the entity is selected or not.
*/

View file

@ -8,7 +8,7 @@ import { EditCollectionTagsComponent } from './_modals/edit-collection-tags/edit
import { ChangeCoverImageModalComponent } from './_modals/change-cover-image/change-cover-image-modal.component';
import { BookmarksModalComponent } from './_modals/bookmarks-modal/bookmarks-modal.component';
import { LazyLoadImageModule } from 'ng-lazyload-image';
import { NgbTooltipModule, NgbCollapseModule, NgbPaginationModule, NgbDropdownModule, NgbProgressbarModule, NgbNavModule, NgbAccordionModule, NgbRatingModule } from '@ng-bootstrap/ng-bootstrap';
import { NgbTooltipModule, NgbCollapseModule, NgbPaginationModule, NgbDropdownModule, NgbProgressbarModule, NgbNavModule, NgbRatingModule } from '@ng-bootstrap/ng-bootstrap';
import { CardActionablesComponent } from './card-item/card-actionables/card-actionables.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { NgxFileDropModule } from 'ngx-file-drop';
@ -23,6 +23,7 @@ import { BulkAddToCollectionComponent } from './_modals/bulk-add-to-collection/b
import { PipeModule } from '../pipe/pipe.module';
import { ChapterMetadataDetailComponent } from './chapter-metadata-detail/chapter-metadata-detail.component';
import { FileInfoComponent } from './file-info/file-info.component';
import { BookmarkComponent } from './bookmark/bookmark.component';
@ -42,7 +43,8 @@ import { FileInfoComponent } from './file-info/file-info.component';
BulkOperationsComponent,
BulkAddToCollectionComponent,
ChapterMetadataDetailComponent,
FileInfoComponent
FileInfoComponent,
BookmarkComponent,
],
imports: [
CommonModule,