Reading List Polish (#1879)
* Use Reading Order to count epub pages rather than raw HTML files. * Send email on background thread for initial invite flow. * Reorder default writing style for new users so Horizontal is default * Changed reading activity to use average hours read rather than events to bring more meaningful data. * added ability to start reading incognito from the top of series detail, needs a bit of styling help though. * Refactored extensions out into their own package, added new fields for reading list to cover total run, cbl import now takes those dates and overrides on import. Replaced many instances of numbers to be comma separated. * Added ability to edit reading list run start and end year/month. Refactored some code for valid month/year into a helper method. * Added a way to see the reading list's release years. * Added some merged image code, but had to remove due to cover dimensions not fixed. * tweaked style for accessibility mode on reading list items * Tweaked css for non virtualized and virtualized containers * Fixed release updates failing * Commented out the merge code. * Typo on words read per year * Fixed unit tests * Fixed virtualized scroll * Cleanup CSS
This commit is contained in:
parent
266f302823
commit
fd6ee42f5f
60 changed files with 2847 additions and 430 deletions
|
@ -2,6 +2,6 @@
|
|||
* Mode the user is reading the book in. Not applicable with ReaderMode.Webtoon
|
||||
*/
|
||||
export enum WritingStyle{
|
||||
Vertical = 0,
|
||||
Horizontal = 1
|
||||
Horizontal = 0,
|
||||
Vertical = 1,
|
||||
}
|
||||
|
|
|
@ -30,4 +30,8 @@ export interface ReadingList {
|
|||
* If this is empty or null, the cover image isn't set. Do not use this externally.
|
||||
*/
|
||||
coverImage: string;
|
||||
startingYear: number;
|
||||
startingMonth: number;
|
||||
endingYear: number;
|
||||
endingMonth: number;
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
<h2 title>
|
||||
{{title}}
|
||||
</h2>
|
||||
<h6 subtitle *ngIf="pagination">{{pagination.totalItems}} Series</h6>
|
||||
<h6 subtitle *ngIf="pagination">{{pagination.totalItems | number}} Series</h6>
|
||||
</app-side-nav-companion-bar>
|
||||
<app-bulk-operations [actionCallback]="bulkActionCallback"></app-bulk-operations>
|
||||
<app-card-detail-layout
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<h2 title>
|
||||
Bookmarks
|
||||
</h2>
|
||||
<h6 subtitle>{{series.length}} Series</h6>
|
||||
<h6 subtitle>{{series.length | number}} Series</h6>
|
||||
</app-side-nav-companion-bar>
|
||||
<app-bulk-operations [actionCallback]="bulkActionCallback"></app-bulk-operations>
|
||||
<app-card-detail-layout
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
<span class="highlight">
|
||||
<i class="fa fa-check me-1" aria-hidden="true"></i>
|
||||
{{selectionCount}} items selected
|
||||
{{selectionCount | number}} items selected
|
||||
</span>
|
||||
|
||||
<span>
|
||||
|
|
|
@ -102,7 +102,7 @@
|
|||
|
||||
virtual-scroller.empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
display: inline-block;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<h2 title>
|
||||
Collections
|
||||
</h2>
|
||||
<h6 subtitle>{{collections.length}} Items</h6>
|
||||
<h6 subtitle>{{collections.length | number}} Items</h6>
|
||||
</app-side-nav-companion-bar>
|
||||
<app-card-detail-layout
|
||||
[isLoading]="isLoading"
|
||||
|
|
|
@ -40,6 +40,6 @@
|
|||
</ul>
|
||||
</div>
|
||||
</app-side-nav-companion-bar>
|
||||
<h6 subtitle class="subtitle-with-actionables" *ngIf="active.fragment === ''">{{pagination.totalItems}} Series</h6>
|
||||
<h6 subtitle class="subtitle-with-actionables" *ngIf="active.fragment === ''">{{pagination.totalItems | number}} Series</h6>
|
||||
<app-bulk-operations [actionCallback]="bulkActionCallback"></app-bulk-operations>
|
||||
<div [ngbNavOutlet]="nav"></div>
|
||||
|
|
|
@ -213,10 +213,13 @@ NZ0ZV4zm4/L1dfnYNCrjTFq9G03rmj5D+Y4i0OHuL3GFPJytaM54AAAAAElFTkSuQmCC
|
|||
</div>
|
||||
<div class="ms-1">
|
||||
<app-series-format [format]="item.format"></app-series-format>
|
||||
<span *ngIf="item.name.toLowerCase().trim().indexOf(searchTerm) >= 0; else localizedName">{{item.name}}</span>
|
||||
<ng-template #localizedName>
|
||||
<span [innerHTML]="item.localizedName"></span>
|
||||
</ng-template>
|
||||
<ng-container *ngIf="searchTerm.toLowerCase().trim() as st">
|
||||
<span *ngIf="item.name.toLowerCase().trim().indexOf(st) >= 0; else localizedName">{{item.name}}</span>
|
||||
<ng-template #localizedName>
|
||||
<span [innerHTML]="item.localizedName"></span>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
<div class="text-light fst-italic" style="font-size: 0.8rem;">in {{item.libraryName}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,25 +1,58 @@
|
|||
<div cdkDropList class="{{items.length > 0 ? 'example-list list-group-flush' : ''}}" (cdkDropListDropped)="drop($event)">
|
||||
<div class="example-box" *ngFor="let item of items; index as i" cdkDrag [cdkDragData]="item" cdkDragBoundary=".example-list">
|
||||
<div class="d-flex list-container">
|
||||
<div class="me-3 align-middle">
|
||||
<i class="fa fa-grip-vertical drag-handle" aria-hidden="true" cdkDragHandle></i>
|
||||
</div>
|
||||
|
||||
<ng-container [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: item, idx: i }"></ng-container>
|
||||
|
||||
<div class="align-middle" style="padding-top: 40px">
|
||||
<label for="reorder-{{i}}" class="form-label visually-hidden">Reorder</label>
|
||||
<input *ngIf="accessibilityMode" id="reorder-{{i}}" class="form-control" type="number" min="0" [max]="items.length - 1" [value]="i" style="width: 60px" (focusout)="updateIndex(i, item)" (keydown.enter)="updateIndex(i, item)" aria-describedby="instructions">
|
||||
</div>
|
||||
<button class="btn btn-icon float-end" (click)="removeItem(item, i)" *ngIf="showRemoveButton">
|
||||
<i class="fa fa-times" aria-hidden="true"></i>
|
||||
<span class="visually-hidden" attr.aria-labelledby="item.id--{{i}}">Remove item</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<ng-container>
|
||||
|
||||
<p class="visually-hidden" id="instructions">
|
||||
When you put a number in the reorder input, the item will be inserted at that location and all other items will have their order updated.
|
||||
</p>
|
||||
<ng-container *ngIf="items.length > 100; else dragList">
|
||||
<div class="example-list list-group-flush">
|
||||
<virtual-scroller #scroll [items]="items" [bufferAmount]="BufferAmount" [parentScroll]="parentScroll">
|
||||
<div class="example-box" *ngFor="let item of scroll.viewPortItems; index as i; trackBy: trackByIdentity">
|
||||
<div class="d-flex list-container">
|
||||
<div class="me-3 align-middle">
|
||||
<div style="padding-top: 40px">
|
||||
<label for="reorder-{{i}}" class="form-label visually-hidden">Reorder</label>
|
||||
<input *ngIf="accessibilityMode" id="reorder-{{i}}" class="form-control" type="number" min="0" [max]="items.length - 1" [value]="i" style="width: 60px"
|
||||
(focusout)="updateIndex(i, item)" (keydown.enter)="updateIndex(i, item)" aria-describedby="instructions">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-container [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: item, idx: i }"></ng-container>
|
||||
|
||||
<button class="btn btn-icon float-end" (click)="removeItem(item, i)" *ngIf="showRemoveButton">
|
||||
<i class="fa fa-times" aria-hidden="true"></i>
|
||||
<span class="visually-hidden" attr.aria-labelledby="item.id--{{i}}">Remove item</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</virtual-scroller>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #dragList>
|
||||
<div cdkDropList class="{{items.length > 0 ? 'example-list list-group-flush' : ''}}" (cdkDropListDropped)="drop($event)">
|
||||
<div class="example-box" *ngFor="let item of items; index as i" cdkDrag [cdkDragData]="item" cdkDragBoundary=".example-list">
|
||||
<div class="d-flex list-container">
|
||||
<div class="me-3 align-middle">
|
||||
<div class="align-middle" style="padding-top: 40px" *ngIf="accessibilityMode">
|
||||
<label for="reorder-{{i}}" class="form-label visually-hidden">Reorder</label>
|
||||
<input id="reorder-{{i}}" class="form-control" type="number" min="0" [max]="items.length - 1" [value]="i" style="width: 60px"
|
||||
(focusout)="updateIndex(i, item)" (keydown.enter)="updateIndex(i, item)" aria-describedby="instructions">
|
||||
</div>
|
||||
<i *ngIf="!accessibilityMode" class="fa fa-grip-vertical drag-handle" aria-hidden="true" cdkDragHandle></i>
|
||||
</div>
|
||||
|
||||
<ng-container [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: item, idx: i }"></ng-container>
|
||||
|
||||
<button class="btn btn-icon float-end" (click)="removeItem(item, i)" *ngIf="showRemoveButton">
|
||||
<i class="fa fa-times" aria-hidden="true"></i>
|
||||
<span class="visually-hidden" attr.aria-labelledby="item.id--{{i}}">Remove item</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
|
||||
|
||||
<p class="visually-hidden" id="instructions">
|
||||
When you put a number in the reorder input, the item will be inserted at that location and all other items will have their order updated.
|
||||
</p>
|
||||
|
||||
|
||||
</ng-container>
|
|
@ -9,11 +9,12 @@
|
|||
|
||||
.example-box {
|
||||
margin: 5px 0;
|
||||
//border-bottom: solid 1px #ccc;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
box-sizing: border-box;
|
||||
font-size: 14px;
|
||||
max-height: 140px;
|
||||
height: 140px;
|
||||
|
||||
.drag-handle {
|
||||
cursor: move;
|
||||
|
@ -59,3 +60,14 @@
|
|||
border-radius: 5px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.virtual-scroller, virtual-scroller {
|
||||
width: 100%;
|
||||
height: calc(var(--vh) * 100 - 173px);
|
||||
}
|
||||
|
||||
virtual-scroller.empty {
|
||||
display: none;
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, EventEmitter, Input, Output, TemplateRef } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, EventEmitter, Input, Output, TemplateRef, TrackByFunction, ViewChild } from '@angular/core';
|
||||
import { VirtualScrollerComponent } from '@iharbeck/ngx-virtual-scroller';
|
||||
|
||||
export interface IndexUpdateEvent {
|
||||
fromPosition: number;
|
||||
|
@ -26,10 +27,19 @@ export class DraggableOrderedListComponent {
|
|||
*/
|
||||
@Input() showRemoveButton: boolean = true;
|
||||
@Input() items: Array<any> = [];
|
||||
/**
|
||||
* Parent scroll for virtualize pagination
|
||||
*/
|
||||
@Input() parentScroll!: Element | Window;
|
||||
@Input() trackByIdentity: TrackByFunction<any> = (index: number, item: any) => `${item.id}_${item.order}_${item.title}`;
|
||||
@Output() orderUpdated: EventEmitter<IndexUpdateEvent> = new EventEmitter<IndexUpdateEvent>();
|
||||
@Output() itemRemove: EventEmitter<ItemRemoveEvent> = new EventEmitter<ItemRemoveEvent>();
|
||||
@ContentChild('draggableItem') itemTemplate!: TemplateRef<any>;
|
||||
|
||||
get BufferAmount() {
|
||||
return Math.min(this.items.length / 20, 20);
|
||||
}
|
||||
|
||||
constructor(private readonly cdRef: ChangeDetectorRef) { }
|
||||
|
||||
drop(event: CdkDragDrop<string[]>) {
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
<app-side-nav-companion-bar [hasExtras]="readingList !== undefined" [extraDrawer]="extrasDrawer">
|
||||
<h2 title>
|
||||
<span *ngIf="actions.length > 0">
|
||||
<app-card-actionables (actionHandler)="performAction($event)" [actions]="actions" [attr.labelBy]="readingList?.title"></app-card-actionables>
|
||||
</span>
|
||||
<app-card-actionables (actionHandler)="performAction($event)" [actions]="actions" [attr.labelBy]="readingList?.title" *ngIf="actions.length > 0"></app-card-actionables>
|
||||
{{readingList?.title}}
|
||||
<span *ngIf="readingList?.promoted" class="ms-1">(<i class="fa fa-angle-double-up" aria-hidden="true"></i>)</span>
|
||||
</h2>
|
||||
<h6 subtitle class="subtitle-with-actionables">{{items.length}} Items</h6>
|
||||
<h6 subtitle class="subtitle-with-actionables">{{items.length | number}} Items</h6>
|
||||
|
||||
<ng-template #extrasDrawer let-offcanvas>
|
||||
<div style="margin-top: 56px" *ngIf="readingList">
|
||||
|
@ -36,7 +34,7 @@
|
|||
</div>
|
||||
</ng-template>
|
||||
</app-side-nav-companion-bar>
|
||||
<div class="container-fluid mt-2" *ngIf="readingList">
|
||||
<div class="container-fluid mt-2" *ngIf="readingList" >
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2 col-xs-4 col-sm-6 d-none d-sm-block" *ngIf="readingList.coverImage !== '' && readingList.coverImage !== undefined && readingList.coverImage !== null">
|
||||
|
@ -83,6 +81,20 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-0 mt-2" *ngIf="readingList.startingYear !== 0">
|
||||
<h4 class="reading-list-years">
|
||||
<ng-container *ngIf="readingList.startingMonth > 0">{{readingList.startingMonth | date:'MMM'}}</ng-container>
|
||||
<ng-container *ngIf="readingList.startingMonth > 0 && readingList.startingYear > 0">, </ng-container>
|
||||
<ng-container *ngIf="readingList.startingYear > 0">{{readingList.startingYear}}</ng-container>
|
||||
—
|
||||
<ng-container *ngIf="readingList.endingYear > 0">
|
||||
<ng-container *ngIf="readingList.endingMonth > 0">{{readingList.endingMonth}}</ng-container>
|
||||
<ng-container *ngIf="readingList.endingMonth > 0 && readingList.endingYear > 0">, </ng-container>
|
||||
<ng-container *ngIf="readingList.endingYear > 0">{{readingList.endingYear}}</ng-container>
|
||||
</ng-container>
|
||||
|
||||
</h4>
|
||||
</div>
|
||||
<!-- Summary row-->
|
||||
<div class="row g-0 mt-2">
|
||||
<app-read-more [text]="readingListSummary" [maxLength]="250"></app-read-more>
|
||||
|
@ -92,22 +104,18 @@
|
|||
|
||||
<div class="row mb-3">
|
||||
<ng-container *ngIf="characters$ | async as characters">
|
||||
<div class="row g-0" *ngIf="characters && characters.length > 0">
|
||||
<div class="col-md-4">
|
||||
<h5>Characters</h5>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<app-badge-expander [items]="characters">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
<div class="row" *ngIf="characters && characters.length > 0">
|
||||
<h5>Characters</h5>
|
||||
<app-badge-expander [items]="characters">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3" cdkScrollable>
|
||||
<div class="row mb-3 scroll-container" #scrollingBlock>
|
||||
<div class="mx-auto" style="width: 200px;">
|
||||
<ng-container *ngIf="items.length === 0 && !isLoading">
|
||||
Nothing added
|
||||
|
@ -115,10 +123,10 @@
|
|||
<app-loading [loading]="isLoading"></app-loading>
|
||||
</div>
|
||||
|
||||
<!-- TODO: This needs virtualization -->
|
||||
<app-draggable-ordered-list [items]="items" (orderUpdated)="orderUpdated($event)" [accessibilityMode]="accessibilityMode" [showRemoveButton]="false">
|
||||
<app-draggable-ordered-list [items]="items" (orderUpdated)="orderUpdated($event)" [accessibilityMode]="accessibilityMode"
|
||||
[showRemoveButton]="false">
|
||||
<ng-template #draggableItem let-item let-position="idx">
|
||||
<app-reading-list-item class="content-container" [item]="item" [position]="position" [libraryTypes]="libraryTypes"
|
||||
<app-reading-list-item [ngClass]="{'content-container': items.length < 100, 'non-virtualized-container': items.length >= 100}" [item]="item" [position]="position" [libraryTypes]="libraryTypes"
|
||||
[promoted]="item.promoted" (read)="readChapter($event)" (remove)="itemRemoved($event, position)"></app-reading-list-item>
|
||||
</ng-template>
|
||||
</app-draggable-ordered-list>
|
||||
|
|
|
@ -2,9 +2,31 @@
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.non-virtualized-container {
|
||||
width: 100%;
|
||||
max-height: 140px;
|
||||
height: 140px;
|
||||
}
|
||||
|
||||
.dropdown-toggle-split {
|
||||
border-top-right-radius: 6px !important;
|
||||
border-bottom-right-radius: 6px !important;
|
||||
border-top-left-radius: 0px !important;
|
||||
border-bottom-left-radius: 0px !important;
|
||||
}
|
||||
|
||||
.reading-list-years {
|
||||
color: var(--input-placeholder-color);
|
||||
}
|
||||
|
||||
.scroll-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
height: calc((var(--vh) *100) - 173px);
|
||||
margin-bottom: 10px;
|
||||
|
||||
&.empty {
|
||||
height: auto;
|
||||
}
|
||||
}
|
|
@ -36,8 +36,10 @@
|
|||
<a href="/library/{{item.libraryId}}/series/{{item.seriesId}}">{{item.seriesName}}</a>
|
||||
</div>
|
||||
|
||||
<!-- TODO: Let's add summary here-->
|
||||
|
||||
<div class="ps-1 mt-2" *ngIf="item.releaseDate !== '0001-01-01T00:00:00'">
|
||||
Released: {{item.releaseDate | date:'short'}}
|
||||
Released: {{item.releaseDate | date:'longDate'}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<app-card-actionables [actions]="globalActions" (actionHandler)="performGlobalAction($event)"></app-card-actionables>
|
||||
<span>Reading Lists</span>
|
||||
</h2>
|
||||
<h6 subtitle *ngIf="pagination">{{pagination.totalItems}} Items</h6>
|
||||
<h6 subtitle *ngIf="pagination">{{pagination.totalItems | number}} Items</h6>
|
||||
</app-side-nav-companion-bar>
|
||||
|
||||
<app-card-detail-layout
|
||||
|
|
|
@ -36,6 +36,60 @@
|
|||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mb-3">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<h6 id="starting-year-header">Starting</h6>
|
||||
<div class="col-md-6 col-sm-12" *ngIf="reviewGroup.get('startingMonth') as formControl" style="width: 90%">
|
||||
<label for="start-month" class="form-label">Month</label>
|
||||
<input id="start-month" class="form-control" formControlName="startingMonth"
|
||||
type="number" [class.is-invalid]="formControl?.invalid && formControl?.touched"
|
||||
aria-describedby="starting-year-header">
|
||||
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="reviewGroup.dirty || reviewGroup.touched">
|
||||
<div *ngIf="formControl.errors?.min || formControl.errors?.max">
|
||||
Must be between 1 and 12 or blank
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-12" *ngIf="reviewGroup.get('startingYear') as formControl" style="width: 90%">
|
||||
<label for="start-year" class="form-label">Year</label>
|
||||
<input id="start-year" class="form-control" formControlName="startingYear" type="number"
|
||||
[class.is-invalid]="formControl.invalid && formControl.touched"
|
||||
aria-describedby="starting-year-header">
|
||||
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="reviewGroup.dirty || reviewGroup.touched">
|
||||
<div *ngIf="formControl.errors?.min || formControl.errors?.max">
|
||||
Must be between 1 and 12 or blank
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<h6 id="ending-year-heading">Ending</h6>
|
||||
<div class="col-md-6 col-sm-12" *ngIf="reviewGroup.get('endingMonth') as formControl" style="width: 90%">
|
||||
<label for="library-name" class="form-label">Month</label>
|
||||
<input id="library-name" class="form-control" formControlName="endingMonth" type="number"
|
||||
[class.is-invalid]="formControl?.invalid && formControl?.touched"
|
||||
aria-describedby="ending-year-header">
|
||||
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="reviewGroup.dirty || reviewGroup.touched">
|
||||
<div *ngIf="formControl.errors?.min || formControl.errors?.max">
|
||||
Must be between 1 and 12 or blank
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-12" *ngIf="reviewGroup.get('endingYear') as formControl" style="width: 90%">
|
||||
<label for="library-name" class="form-label">Year</label>
|
||||
<input id="library-name" class="form-control" formControlName="endingYear" type="number"
|
||||
[class.is-invalid]="formControl?.invalid && formControl?.touched"
|
||||
aria-describedby="ending-year-header">
|
||||
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="reviewGroup.dirty || reviewGroup.touched">
|
||||
<div *ngIf="formControl.errors?.min || formControl.errors?.max">
|
||||
Must be between 1 and 12 or blank
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mb-3">
|
||||
<label for="summary" class="form-label">Summary</label>
|
||||
|
|
|
@ -49,6 +49,10 @@ export class EditReadingListModalComponent implements OnInit, OnDestroy {
|
|||
title: new FormControl(this.readingList.title, { nonNullable: true, validators: [Validators.required] }),
|
||||
summary: new FormControl(this.readingList.summary, { nonNullable: true, validators: [] }),
|
||||
promoted: new FormControl(this.readingList.promoted, { nonNullable: true, validators: [] }),
|
||||
startingMonth: new FormControl(this.readingList.startingMonth, { nonNullable: true, validators: [Validators.min(1), Validators.max(12)] }),
|
||||
startingYear: new FormControl(this.readingList.startingYear, { nonNullable: true, validators: [Validators.min(1000)] }),
|
||||
endingMonth: new FormControl(this.readingList.endingMonth, { nonNullable: true, validators: [Validators.min(1), Validators.max(12)] }),
|
||||
endingYear: new FormControl(this.readingList.endingYear, { nonNullable: true, validators: [Validators.min(1000)] }),
|
||||
});
|
||||
|
||||
this.reviewGroup.get('title')?.valueChanges.pipe(
|
||||
|
@ -90,6 +94,10 @@ export class EditReadingListModalComponent implements OnInit, OnDestroy {
|
|||
if (this.reviewGroup.value.title.trim() === '') return;
|
||||
|
||||
const model = {...this.reviewGroup.value, readingListId: this.readingList.id, coverImageLocked: this.coverImageLocked};
|
||||
model.startingMonth = model.startingMonth || 0;
|
||||
model.startingYear = model.startingYear || 0;
|
||||
model.endingMonth = model.endingMonth || 0;
|
||||
model.endingYear = model.endingYear || 0;
|
||||
const apis = [this.readingListService.update(model)];
|
||||
|
||||
if (this.selectedCover !== '') {
|
||||
|
|
|
@ -18,6 +18,7 @@ import { FileUploadModule } from '@iplab/ngx-file-upload';
|
|||
import { CblConflictReasonPipe } from './_pipes/cbl-conflict-reason.pipe';
|
||||
import { StepTrackerComponent } from './_components/step-tracker/step-tracker.component';
|
||||
import { CblImportResultPipe } from './_pipes/cbl-import-result.pipe';
|
||||
import { VirtualScrollerModule } from '@iharbeck/ngx-virtual-scroller';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -48,6 +49,7 @@ import { CblImportResultPipe } from './_pipes/cbl-import-result.pipe';
|
|||
ReadingListRoutingModule,
|
||||
NgbAccordionModule, // Import CBL
|
||||
FileUploadModule, // Import CBL
|
||||
VirtualScrollerModule,
|
||||
],
|
||||
exports: [
|
||||
AddToListModalComponent,
|
||||
|
|
|
@ -73,13 +73,29 @@
|
|||
<div class="col-xlg-10 col-lg-8 col-md-8 col-xs-8 col-sm-6 mt-2">
|
||||
<div class="row g-0">
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-primary" (click)="read()">
|
||||
<span>
|
||||
<i class="fa {{showBook ? 'fa-book-open' : 'fa-book'}} me-1"></i>
|
||||
</span>
|
||||
<span class="d-none d-sm-inline-block">{{(hasReadingProgress) ? 'Continue' : 'Read'}}</span>
|
||||
</button>
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-primary" (click)="read()">
|
||||
<span>
|
||||
<i class="fa {{showBook ? 'fa-book-open' : 'fa-book'}}" aria-hidden="true"></i>
|
||||
<span class="read-btn--text"> {{(hasReadingProgress) ? 'Continue' : 'Read'}}</span>
|
||||
</span>
|
||||
</button>
|
||||
<div class="btn-group" ngbDropdown role="group" display="dynamic" aria-label="Read options">
|
||||
<button type="button" class="btn btn-primary dropdown-toggle-split" ngbDropdownToggle></button>
|
||||
<div class="dropdown-menu" ngbDropdownMenu>
|
||||
<button ngbDropdownItem (click)="read(true)">
|
||||
<span>
|
||||
<i class="fa fa-glasses" aria-hidden="true"></i>
|
||||
<span class="read-btn--text"> {{(hasReadingProgress) ? 'Continue' : 'Read'}} Incognito</span>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-auto ms-2">
|
||||
<button class="btn btn-secondary" (click)="toggleWantToRead()" title="{{isWantToRead ? 'Remove from' : 'Add to'}} Want to Read">
|
||||
<span>
|
||||
|
|
|
@ -56,4 +56,11 @@
|
|||
|
||||
::ng-deep .progress {
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
.dropdown-toggle-split {
|
||||
border-top-right-radius: 6px !important;
|
||||
border-bottom-right-radius: 6px !important;
|
||||
border-top-left-radius: 0px !important;
|
||||
border-bottom-left-radius: 0px !important;
|
||||
}
|
|
@ -654,14 +654,14 @@ export class SeriesDetailComponent implements OnInit, OnDestroy, AfterContentChe
|
|||
});
|
||||
}
|
||||
|
||||
read() {
|
||||
read(incognitoMode: boolean = false) {
|
||||
if (this.currentlyReadingChapter !== undefined) {
|
||||
this.openChapter(this.currentlyReadingChapter);
|
||||
this.openChapter(this.currentlyReadingChapter, incognitoMode);
|
||||
return;
|
||||
}
|
||||
|
||||
this.readerService.getCurrentChapter(this.seriesId).subscribe(chapter => {
|
||||
this.openChapter(chapter);
|
||||
this.openChapter(chapter, incognitoMode);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { SeriesDetailRoutingModule } from './series-detail-routing.module';
|
||||
import { NgbCollapseModule, NgbNavModule, NgbProgressbarModule, NgbRatingModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { NgbCollapseModule, NgbDropdownModule, NgbNavModule, NgbProgressbarModule, NgbRatingModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { SeriesMetadataDetailComponent } from './_components/series-metadata-detail/series-metadata-detail.component';
|
||||
import { ReviewSeriesModalComponent } from './_modals/review-series-modal/review-series-modal.component';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
|
@ -27,6 +27,7 @@ import { SeriesDetailComponent } from './_components/series-detail/series-detail
|
|||
NgbRatingModule,
|
||||
NgbTooltipModule, // Series Detail, Extras Drawer
|
||||
NgbProgressbarModule,
|
||||
NgbDropdownModule,
|
||||
|
||||
TypeaheadModule,
|
||||
PipeModule,
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
[roundDomains]="true"
|
||||
[autoScale]="true"
|
||||
xAxisLabel="Time"
|
||||
yAxisLabel="Reading Activity"
|
||||
yAxisLabel="Hours Read"
|
||||
[timeline]="false"
|
||||
[results]="data"
|
||||
>
|
||||
|
|
|
@ -34,7 +34,7 @@ export class UserStatsInfoCardsComponent {
|
|||
const numberPipe = new CompactNumberPipe();
|
||||
this.statsService.getWordsPerYear().subscribe(yearCounts => {
|
||||
const ref = this.modalService.open(GenericListModalComponent, { scrollable: true });
|
||||
ref.componentInstance.items = yearCounts.map(t => `${t.name}: ${numberPipe.transform(t.value)} pages`);
|
||||
ref.componentInstance.items = yearCounts.map(t => `${t.name}: ${numberPipe.transform(t.value)} words`);
|
||||
ref.componentInstance.title = 'Words Read By Year';
|
||||
});
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
Want To Read
|
||||
</h2>
|
||||
</ng-container>
|
||||
<h6 subtitle>{{seriesPagination.totalItems}} Series</h6>
|
||||
<h6 subtitle>{{seriesPagination.totalItems | number}} Series</h6>
|
||||
</app-side-nav-companion-bar>
|
||||
</div>
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue